Layout
An Adwaita window is a frame plus the containers that give it shape. The window is the frame itself; a bin holds one replaceable widget where a slot calls for it, a clamp holds content at a readable width (and a scrollable clamp when that content scrolls), a header bar is the top chrome, a toolbar view frames content between fixed bars, and a wrap box lets a run of children flow onto new lines. None of them hold data: they position what you put inside and adapt as the space around them changes.
The last three are the adaptive ones. A breakpoint bin gives breakpoints a home without a window, a multi-layout view holds one set of children and several arrangements of them, and a layout slot is the named hole one of those arrangements leaves for a child.
The ports, and what they follow
Libadwaita is the reference, typed by
@girs/adw-1.
@gjsify/adwaita-web
and @gjsify/adwaita-nativescript
follow its naming, and where a port differs, the window that shows it says so. Both
answer gi://Adw and gi://Gtk. On NativeScript that resolves once you build with
gjsify build --app nativescript --gi-renderer.
The previews run the browser port, so behaviour Libadwaita drives natively (a toolbar view revealing each bar independently, a clamp’s tightening threshold) can be simplified here.
Window
Section titled “Window”The frame everything else goes into. It styles no chrome of its own: you give it a
content widget — normally a toolbar view with a header bar — and
it asks for the size Adw.Window asks for, 360 × 200 px, unless you set a default
size yourself.
The preview is a window drawn in the page
Adw.Window is a toplevel. On the desktop it is a real window that the compositor
places, maximises, tiles and closes. A browser document has no window manager, so
<adw-window> is a framed box in the page: the frame libadwaita draws for a
client-side-decorated window, the content property, the breakpoints and the
open-dialog bookkeeping — and none of what a compositor owns, so no titlebar, no
minimise button and no window placement. What you put inside it behaves the same.
using Gtk 4.0;using Adw 1;
Adw.Window { default-width: 440; default-height: 240;
content: Adw.ToolbarView { [top] Adw.HeaderBar { title-widget: Adw.WindowTitle { title: _("Text Editor"); subtitle: _("notes.md"); }; }
content: Adw.StatusPage { title: _("Your Library"); description: _("Content sits between the toolbars and scrolls independently of them."); }; };}import Adw from 'gi://Adw?version=1';
Adw.init();
const headerBar = new Adw.HeaderBar();headerBar.set_title_widget(new Adw.WindowTitle({ title: 'Text Editor', subtitle: 'notes.md' }));
const toolbarView = new Adw.ToolbarView({ top_bar: headerBar });toolbarView.content = new Adw.StatusPage({ title: 'Your Library', description: 'Content sits between the toolbars and scrolls independently of them.',});
// ONE child, and it is a property: `content` takes the widget itself, so this is// never an `append()`.const window = new Adw.Window({ content: toolbarView });
// The size request `adw_window_init()` makes is 360×200 (adw-window.c:344).window.set_default_size(440, 240);<adw-window width="440" height="240"> <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md"></adw-window-title> </adw-header-bar> <!-- description: The description markup to be displayed below the title. --> <adw-status-page slot="content" title="Your Library" description="Content sits between the toolbars and scrolls independently of them." ></adw-status-page> </adw-toolbar-view></adw-window>// mount(() => <AdwWindow />, container) — from '@gjsify/gtk-host/solid'const AdwWindow = () => ( <adw-window defaultWidth={440} defaultHeight={240} > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md" /> </adw-header-bar> <adw-status-page slot="content" title="Your Library" description="Content sits between the toolbars and scrolls independently of them." /> </adw-toolbar-view> </adw-window>);<!-- AdwWindow.vue — mount(AdwWindow, container) from '@gjsify/gtk-host/vue' --><template> <adw-window :default-width="440" :default-height="240" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md" /> </adw-header-bar> <adw-status-page slot="content" title="Your Library" description="Content sits between the toolbars and scrolls independently of them." /> </adw-toolbar-view> </adw-window></template>// createRoot(container).render(<AdwWindow />) — from '@gjsify/gtk-host/react'const AdwWindow = () => ( <adw-window defaultWidth={440} defaultHeight={240} > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md" /> </adw-header-bar> <adw-status-page slot="content" title="Your Library" description="Content sits between the toolbars and scrolls independently of them." /> </adw-toolbar-view> </adw-window>);The NativeScript tabs were inflated on a device
adwaita-gallery-nativescript
loads each template through NativeScript’s own Builder and asserts the view tree it
built. That is the only way to tell a template that renders from one that renders
something else: an attribute arrives as a string, and a child a widget has no slot for
is added to the layout anyway.
A NativeScript template reaches these widgets through its xmlns, and the namespace is
a MODULE: NativeScript loads it and reads the element name off its exports, so
xmlns:adw="~/adw" needs an app-local app/adw.ts that re-exports what the markup
names. A bare package specifier there does not resolve. There is one barrel per library,
~/adw for Adw.* and ~/gtk for Gtk.*, because the prefix is the only thing in XML
that can say which library a widget belongs to. Point both at one module and
<adw:Button> silently builds the GTK button.
The wrap box only recently joined them. @gjsify/gtk-host had no child policy for
AdwWrapBox, so a child placed in one was refused by name. That was deliberate: a
renderer guessing the policy would call add, append or set_child and be wrong at
exit 0. The policy is now curated against libadwaita, so the block has a tree like the
other three.
React Native primitives go in these containers too
The Adwaita element is the same one the React tab shows, and a <View> or <Text> sits
in its default slot. The clamp, the header bar and the toolbar view carried a tab for
that pairing, and they were the only three blocks on the site with one, so the React
Native page carries it in full
instead. Two facts from there are worth having before you try it. The primitives need
their token scales installed with configureStyle({ tokens }) before the first render,
because a class naming a token you have not declared throws out of the render and leaves
an empty window at exit 0 with nothing in the log. And such a tree is a GTK window
rather than a phone screen, which is why it was never the NativeScript tab.
Application Window
Section titled “Application Window”The window with one thing added: the menubar its application
installed. Turn show menubar off and the bar goes away; turn it on and the window
draws it, which is all Gtk.ApplicationWindow:show-menubar decides.
A browser has no application
On GTK the model belongs to a Gtk.Application, which installs it into every
application window it opens — a document has no application, so
<adw-application-window> takes the menu model itself, in the same portable form
<gtk-menu-button> takes, and reads it with the same parser. A menubar is a row of
menu links: each one becomes a button that opens its menu, and a link’s own items
are what that menu holds.
using Gtk 4.0;using Adw 1;
Adw.ApplicationWindow { // The menubar is the APPLICATION's, so a `.blp` cannot spell it: a section of a // Gio.Menu is a `menu` object and the window takes a PopoverMenuBar, and both are // installed by the code that owns the window. The flag is all markup carries. show-menubar: true; default-width: 440; default-height: 240;
content: Adw.ToolbarView { [top] Adw.HeaderBar { title-widget: Adw.WindowTitle { title: _("Text Editor"); subtitle: _("notes.md"); }; }
content: Adw.StatusPage { title: _("Your Library"); description: _("Content sits between the toolbars and scrolls independently of them."); }; };}import Adw from 'gi://Adw?version=1';import Gio from 'gi://Gio?version=2.0';import Gtk from 'gi://Gtk?version=4.0';
Adw.init();
// The menubar model: a row of LINKS, which is what `Gtk.Application:menubar` is.const menu = new Gio.Menu();const file = new Gio.Menu();file.append('New Window', 'app.new-window');file.append('Quit', 'app.quit');menu.append_section(null, file);
// Gtk.MenuBar is gone from GTK 4; the row is a Gtk.PopoverMenuBar. An app never// adds it — `Gtk.Application` installs the model at construction — and this boolean// is what decides whether the window DRAWS it.const menubar = new Gtk.PopoverMenuBar({ menu_model: menu });
// The window has ONE child, its content. The menubar is the toolbar view's second// top bar, which is where an application window's own menubar sits.const toolbarView = new Adw.ToolbarView();toolbarView.add_top_bar(new Adw.HeaderBar());toolbarView.add_top_bar(menubar);toolbarView.content = new Adw.StatusPage({ title: 'Your Library' });
const window = new Adw.ApplicationWindow({ content: toolbarView, show_menubar: true });window.set_default_size(440, 240);<!-- show-menubar: If this property is true, the window will display a menubar unless it is shown by the desktop shell. --><adw-application-window width="440" height="240" show-menubar menu-model='[{"label":"File","submenu":[{"label":"New Window","action":"app.new-window"},{"label":"Quit","action":"app.quit"}]},{"label":"Help","submenu":[{"label":"Keyboard Shortcuts","action":"app.shortcuts"},{"label":"About","action":"app.about"}]}]'> <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md"></adw-window-title> </adw-header-bar> <!-- description: The description markup to be displayed below the title. --> <adw-status-page slot="content" title="Your Library" description="Content sits between the toolbars and scrolls independently of them." ></adw-status-page> </adw-toolbar-view></adw-application-window>The smallest container libadwaita ships: a widget that holds one child and nothing else. It draws no frame of its own and hands the child its whole allocation, which makes it the thing to reach for when a slot needs a single replaceable widget. Setting a new child replaces the old one rather than stacking beside it, and a child that already has a parent is refused.
using Gtk 4.0;using Adw 1;
Adw.Bin { Gtk.Label { label: _("The one child of this bin."); }}import Adw from 'gi://Adw?version=1';import Gtk from 'gi://Gtk?version=4.0';
const bin = new Adw.Bin({ child: new Gtk.Label({ label: 'The one child of this bin.' }) });
// A second child REPLACES the first.bin.child = new Gtk.Label({ label: 'The first child is gone.' });<adw-bin style="width:260px;"> <gtk-label label="The one child of this bin."></gtk-label></adw-bin>// mount(() => <AdwBin />, container) — from '@gjsify/gtk-host/solid'const AdwBin = () => ( <adw-bin> <gtk-label label="The one child of this bin." wrap xalign={0} cssClasses={['card']} /> </adw-bin>);<!-- AdwBin.vue — mount(AdwBin, container) from '@gjsify/gtk-host/vue' --><template> <adw-bin> <gtk-label label="The one child of this bin." :wrap="true" :xalign="0" :css-classes="['card']" /> </adw-bin></template>// createRoot(container).render(<AdwBin />) — from '@gjsify/gtk-host/react'const AdwBin = () => ( <adw-bin> <gtk-label label="The one child of this bin." wrap xalign={0} cssClasses={['card']} /> </adw-bin>);Wrap long-form content in a clamp so it stops growing at a maximum width and stays centred in anything wider. Below the tightening threshold the clamp starts tightening the child, which keeps text at a comfortable measure on a large display without wasting room on a small one.
Why this block is shaped differently
The layout exists once, as a Blueprint file, and every surface builds that file: GTK
compiles it to GtkBuilder XML, and the web and NativeScript ports build its
?shared-tree projection. The preview is that projection, built by
@gjsify/adwaita-web, and Blueprint under it is the file. The Web Components
and NativeScript tabs of Code hold the files of a project, picked in the small
row under the tab bar. The first is the same layout in the port’s own markup,
index.html or views/clamp.xml, generated from the Blueprint file when the site is
built, so the ids match. The code that goes with it reaches an element by its id, which
is where the platforms differ: the web clamp takes its size as an attribute, the other
two as a property. The file marked (Blueprint) is that same code building the layout
from the Blueprint file instead of the markup.
The first tab is one program for four runtimes. Built with gjsify build --app gjs it
runs on GJS, and built with --app node the same source runs on Node.js, Deno and Bun
through node-gi. build() calls Adw.init() itself, so the builder knows Adw.Clamp
in a bare script too; inside an Adw.Application the call is harmless.
using Gtk 4.0;using Adw 1;
Adw.Clamp clamp { maximum-size: 400; tightening-threshold: 300;
child: Gtk.Label label { label: _("This content is clamped: it stops growing past the maximum size and stays centred."); wrap: true;
styles ["card"] };}import { build } from './clamp.blp';
const { clamp, label } = build();label.label = 'Reached by its Blueprint id.';clamp.maximumSize = 560;<adw-clamp id="clamp" maximum-size="400" tightening-threshold="300"> <gtk-label id="label" label="This content is clamped: it stops growing past the maximum size and stays centred." wrap class="card" slot="child" ></gtk-label></adw-clamp><script type="module"> import '@gjsify/adwaita-web';
const clamp = document.querySelector('#clamp'); const label = document.querySelector('#label'); label.label = 'Reached by its id.'; clamp.setAttribute('maximum-size', '560');</script>import { mountSharedTree } from '@gjsify/adwaita-web';import tree from './clamp.blp?shared-tree';
mountSharedTree(tree);
const clamp = document.querySelector('#clamp');const label = document.querySelector('#label');label.label = 'Reached by its id.';clamp.setAttribute('maximum-size', '560');<adw:Clamp xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adw" xmlns:gtk="~/gtk" id="clamp" maximumSize="400" tighteningThreshold="300"> <adw:Clamp.child> <gtk:Label id="label" label="This content is clamped: it stops growing past the maximum size and stays centred." wrap="true" styleClasses="card" /> </adw:Clamp.child></adw:Clamp>import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';import { Builder } from '@nativescript/core';
const clamp = Builder.load({ path: '~/views', name: 'clamp' }) as Adw.Clamp;const label = clamp.getViewById<Gtk.Label>('label')!;label.label = 'Reached by its id.';clamp.maximumSize = 560;import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';import { build } from '@gjsify/adwaita-nativescript/builder';import tree from './clamp.blp?shared-tree';
const clamp = build(tree) as Adw.Clamp;const label = clamp.getViewById<Gtk.Label>('label')!;label.label = 'Reached by its id.';clamp.maximumSize = 560;// mount(() => <AdwClamp />, container) — from '@gjsify/gtk-host/solid'const AdwClamp = () => ( <adw-clamp maximumSize={400} tighteningThreshold={300} > <gtk-label label="This content is clamped: it stops growing past the maximum size and stays centred." wrap xalign={0} cssClasses={['card']} /> </adw-clamp>);<!-- AdwClamp.vue — mount(AdwClamp, container) from '@gjsify/gtk-host/vue' --><template> <adw-clamp :maximum-size="400" :tightening-threshold="300" > <gtk-label label="This content is clamped: it stops growing past the maximum size and stays centred." :wrap="true" :xalign="0" :css-classes="['card']" /> </adw-clamp></template>// createRoot(container).render(<AdwClamp />) — from '@gjsify/gtk-host/react'const AdwClamp = () => ( <adw-clamp maximumSize={400} tighteningThreshold={300} > <gtk-label label="This content is clamped: it stops growing past the maximum size and stays centred." wrap xalign={0} cssClasses={['card']} /> </adw-clamp>);Clamp Scrollable
Section titled “Clamp Scrollable”A clamp for a child that scrolls. A plain clamp would cut a list or a text view off at its maximum width, scrollbar included; the scrollable clamp keeps the scrollbar at the edge of the window and holds only the content at the maximum size, with the same tightening threshold curve as the clamp above. Its child has to be a scrollable one, a list view or a text view.
using Gtk 4.0;using Adw 1;
Gtk.ScrolledWindow { Adw.ClampScrollable { maximum-size: 320; tightening-threshold: 240;
Gtk.TextView { editable: false; } }}import Adw from 'gi://Adw?version=1';import Gtk from 'gi://Gtk?version=4.0';
const text = new Gtk.TextView({ editable: false });const clamp = new Adw.ClampScrollable({ maximumSize: 320, tighteningThreshold: 240, child: text });
// The scrolled window owns the scrollbars; the clamp only limits the content.const scrolled = new Gtk.ScrolledWindow({ child: clamp });<!-- maximum-size: The maximum size allocated to the child. --><!-- tightening-threshold: The size above which the child is clamped. --><adw-clamp-scrollable maximum-size="320" tightening-threshold="240" style="width:100%;height:120px;"> <gtk-box orientation="vertical" spacing="6"> <gtk-label label="Line 1: held at the clamp's width."></gtk-label> <gtk-label label="Line 2: while the rest scrolls."></gtk-label> <gtk-label label="Line 3: and the scrollbar stays at the edge."></gtk-label> <gtk-label label="Line 4: of the window."></gtk-label> <gtk-label label="Line 5: not at the edge of the content."></gtk-label> </gtk-box></adw-clamp-scrollable>Header Bar
Section titled “Header Bar”The standard top chrome of an Adwaita window. Put a title widget in the centre, usually a window title with a subtitle, and pack controls to the start and end edges: navigation on the leading side, menu and action buttons on the trailing one.
using Gtk 4.0;using Adw 1;
Adw.HeaderBar header_bar { title-widget: Adw.WindowTitle window_title { title: _("Text Editor"); subtitle: "notes.md"; };
[start] Gtk.Button back_button { icon-name: "go-previous-symbolic";
styles ["flat"] }
[end] Gtk.MenuButton menu_button { icon-name: "open-menu-symbolic";
styles ["flat"] }}import Gio from 'gi://Gio?version=2.0';import { build } from './header-bar.blp';
const { window_title, menu_button } = build();window_title.subtitle = 'draft.md';
const menu = new Gio.Menu();menu.append('New Window', 'app.new-window');menu.append('Preferences', 'app.preferences');menu.append('About', 'app.about');menu_button.menuModel = menu;<adw-header-bar id="header_bar"> <adw-window-title id="window_title" title="Text Editor" subtitle="notes.md" slot="title-widget" ></adw-window-title> <gtk-button id="back_button" icon-name="go-previous-symbolic" class="flat" slot="start" ></gtk-button> <gtk-menu-button id="menu_button" icon-name="open-menu-symbolic" class="flat" slot="end" ></gtk-menu-button></adw-header-bar><script type="module"> import '@gjsify/adwaita-web';
const title = document.querySelector('#window_title'); const menuButton = document.querySelector('#menu_button'); title.setAttribute('subtitle', 'draft.md');
menuButton.menuModel = [ { label: 'New Window', action: 'app.new-window' }, { label: 'Preferences', action: 'app.preferences' }, { label: 'About', action: 'app.about' }, ];</script>import { mountSharedTree } from '@gjsify/adwaita-web';import tree from './header-bar.blp?shared-tree';
mountSharedTree(tree);
const title = document.querySelector('#window_title');const menuButton = document.querySelector('#menu_button');title.setAttribute('subtitle', 'draft.md');
menuButton.menuModel = [ { label: 'New Window', action: 'app.new-window' }, { label: 'Preferences', action: 'app.preferences' }, { label: 'About', action: 'app.about' },];<adw:HeaderBar xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adw" xmlns:gtk="~/gtk" id="header_bar"> <adw:HeaderBar.title-widget> <adw:WindowTitle id="window_title" title="Text Editor" subtitle="notes.md" /> </adw:HeaderBar.title-widget> <adw:HeaderBar.start> <gtk:Button id="back_button" iconName="go-previous-symbolic" styleClasses="flat" /> </adw:HeaderBar.start> <adw:HeaderBar.end> <gtk:MenuButton id="menu_button" iconName="open-menu-symbolic" styleClasses="flat" /> </adw:HeaderBar.end></adw:HeaderBar>import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';import { Builder } from '@nativescript/core';
const headerBar = Builder.load({ path: '~/views', name: 'header-bar' }) as Adw.HeaderBar;const title = headerBar.getViewById<Adw.WindowTitle>('window_title')!;const menuButton = headerBar.getViewById<Gtk.MenuButton>('menu_button')!;title.subtitle = 'draft.md';
menuButton.menuModel = [ { label: 'New Window', action: 'app.new-window' }, { label: 'Preferences', action: 'app.preferences' }, { label: 'About', action: 'app.about' },];import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';import { build } from '@gjsify/adwaita-nativescript/builder';import tree from './header-bar.blp?shared-tree';
const headerBar = build(tree) as Adw.HeaderBar;const title = headerBar.getViewById<Adw.WindowTitle>('window_title')!;const menuButton = headerBar.getViewById<Gtk.MenuButton>('menu_button')!;title.subtitle = 'draft.md';
menuButton.menuModel = [ { label: 'New Window', action: 'app.new-window' }, { label: 'Preferences', action: 'app.preferences' }, { label: 'About', action: 'app.about' },];// mount(() => <AdwHeaderBar />, container) — from '@gjsify/gtk-host/solid'const AdwHeaderBar = () => ( <adw-header-bar> <gtk-button slot="start" iconName="go-previous-symbolic" cssClasses={['flat']} /> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md" /> <gtk-menu-button slot="end" iconName="open-menu-symbolic" cssClasses={['flat']} /> </adw-header-bar>);<!-- AdwHeaderBar.vue — mount(AdwHeaderBar, container) from '@gjsify/gtk-host/vue' --><template> <adw-header-bar> <gtk-button slot="start" icon-name="go-previous-symbolic" :css-classes="['flat']" /> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md" /> <gtk-menu-button slot="end" icon-name="open-menu-symbolic" :css-classes="['flat']" /> </adw-header-bar></template>// createRoot(container).render(<AdwHeaderBar />) — from '@gjsify/gtk-host/react'const AdwHeaderBar = () => ( <adw-header-bar> <gtk-button slot="start" iconName="go-previous-symbolic" cssClasses={['flat']} /> <adw-window-title slot="title" title="Text Editor" subtitle="notes.md" /> <gtk-menu-button slot="end" iconName="open-menu-symbolic" cssClasses={['flat']} /> </adw-header-bar>);Toolbar View
Section titled “Toolbar View”Frame your content between a top bar and a bottom bar, typically a header bar above and an action bar below. Each set of bars reveals and hides independently, and the content scrolls underneath, so the chrome stays put while the view moves.
using Gtk 4.0;using Adw 1;
Adw.ToolbarView { [top] Adw.HeaderBar { title-widget: Adw.WindowTitle { title: _("Documents"); subtitle: _("12 items"); }; }
content: Adw.StatusPage { icon-name: "folder-documents-symbolic"; title: _("Your library"); description: _("Content sits between the toolbars and scrolls independently of them."); };
[bottom] Gtk.ActionBar { [start] Gtk.Button add_button { icon-name: "list-add-symbolic"; tooltip-text: _("Add");
styles ["flat"] }
[start] Gtk.Button { icon-name: "list-remove-symbolic"; tooltip-text: _("Remove");
styles ["flat"] }
[center] Gtk.Label selection { label: _("Selection: none"); }
[end] Gtk.Button { icon-name: "send-to-symbolic"; tooltip-text: _("Share");
styles ["flat"] } }}import { build } from './toolbar-view.blp';
const { add_button, selection } = build();add_button.connect('clicked', () => { selection.label = 'Selection: 1 item';});<adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title title="Documents" subtitle="12 items" slot="title-widget" ></adw-window-title> </adw-header-bar> <adw-status-page icon-name="folder-documents-symbolic" title="Your library" description="Content sits between the toolbars and scrolls independently of them." slot="content" ></adw-status-page> <gtk-action-bar slot="bottom"> <gtk-button id="add_button" icon-name="list-add-symbolic" tooltip-text="Add" class="flat" slot="start" ></gtk-button> <gtk-button icon-name="list-remove-symbolic" tooltip-text="Remove" class="flat" slot="start" ></gtk-button> <gtk-label id="selection" label="Selection: none" slot="center"></gtk-label> <gtk-button icon-name="send-to-symbolic" tooltip-text="Share" class="flat" slot="end" ></gtk-button> </gtk-action-bar></adw-toolbar-view><script type="module"> import '@gjsify/adwaita-web';
const add = document.querySelector('#add_button'); const selection = document.querySelector('#selection'); add.addEventListener('click', () => { selection.label = 'Selection: 1 item'; });</script>import { mountSharedTree } from '@gjsify/adwaita-web';import tree from './toolbar-view.blp?shared-tree';
mountSharedTree(tree);
const add = document.querySelector('#add_button');const selection = document.querySelector('#selection');add.addEventListener('click', () => { selection.label = 'Selection: 1 item';});<adw:ToolbarView xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adw" xmlns:gtk="~/gtk"> <adw:ToolbarView.top> <adw:HeaderBar> <adw:HeaderBar.title-widget> <adw:WindowTitle title="Documents" subtitle="12 items" /> </adw:HeaderBar.title-widget> </adw:HeaderBar> </adw:ToolbarView.top> <adw:ToolbarView.content> <adw:StatusPage iconName="folder-documents-symbolic" title="Your library" description="Content sits between the toolbars and scrolls independently of them." /> </adw:ToolbarView.content> <adw:ToolbarView.bottom> <gtk:ActionBar> <gtk:ActionBar.start> <gtk:Button id="add_button" iconName="list-add-symbolic" tooltipText="Add" styleClasses="flat" /> </gtk:ActionBar.start> <gtk:ActionBar.start> <gtk:Button iconName="list-remove-symbolic" tooltipText="Remove" styleClasses="flat" /> </gtk:ActionBar.start> <gtk:ActionBar.center> <gtk:Label id="selection" label="Selection: none" /> </gtk:ActionBar.center> <gtk:ActionBar.end> <gtk:Button iconName="send-to-symbolic" tooltipText="Share" styleClasses="flat" /> </gtk:ActionBar.end> </gtk:ActionBar> </adw:ToolbarView.bottom></adw:ToolbarView>import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';import { Builder } from '@nativescript/core';
const view = Builder.load({ path: '~/views', name: 'toolbar-view' }) as Adw.ToolbarView;const add = view.getViewById<Gtk.Button>('add_button')!;const selection = view.getViewById<Gtk.Label>('selection')!;add.connect('clicked', () => { selection.label = 'Selection: 1 item';});import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';import { build } from '@gjsify/adwaita-nativescript/builder';import tree from './toolbar-view.blp?shared-tree';
const view = build(tree) as Adw.ToolbarView;const add = view.getViewById<Gtk.Button>('add_button')!;const selection = view.getViewById<Gtk.Label>('selection')!;add.connect('clicked', () => { selection.label = 'Selection: 1 item';});// mount(() => <AdwToolbarView />, container) — from '@gjsify/gtk-host/solid'const AdwToolbarView = () => ( <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Documents" subtitle="12 items" /> </adw-header-bar> <adw-status-page slot="content" iconName="folder-documents-symbolic" title="Your library" description="Content sits between the toolbars and scrolls independently of them." /> <gtk-box slot="bottom" cssClasses={['toolbar']} spacing={6} > <gtk-button iconName="list-add-symbolic" cssClasses={['flat']} /> <gtk-button iconName="list-remove-symbolic" cssClasses={['flat']} /> <gtk-label label="Selection: none" hexpand /> <gtk-button iconName="send-to-symbolic" cssClasses={['flat']} /> </gtk-box> </adw-toolbar-view>);<!-- AdwToolbarView.vue — mount(AdwToolbarView, container) from '@gjsify/gtk-host/vue' --><template> <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Documents" subtitle="12 items" /> </adw-header-bar> <adw-status-page slot="content" icon-name="folder-documents-symbolic" title="Your library" description="Content sits between the toolbars and scrolls independently of them." /> <gtk-box slot="bottom" :css-classes="['toolbar']" :spacing="6" > <gtk-button icon-name="list-add-symbolic" :css-classes="['flat']" /> <gtk-button icon-name="list-remove-symbolic" :css-classes="['flat']" /> <gtk-label label="Selection: none" :hexpand="true" /> <gtk-button icon-name="send-to-symbolic" :css-classes="['flat']" /> </gtk-box> </adw-toolbar-view></template>// createRoot(container).render(<AdwToolbarView />) — from '@gjsify/gtk-host/react'const AdwToolbarView = () => ( <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Documents" subtitle="12 items" /> </adw-header-bar> <adw-status-page slot="content" iconName="folder-documents-symbolic" title="Your library" description="Content sits between the toolbars and scrolls independently of them." /> <gtk-box slot="bottom" cssClasses={['toolbar']} spacing={6} > <gtk-button iconName="list-add-symbolic" cssClasses={['flat']} /> <gtk-button iconName="list-remove-symbolic" cssClasses={['flat']} /> <gtk-label label="Selection: none" hexpand /> <gtk-button iconName="send-to-symbolic" cssClasses={['flat']} /> </gtk-box> </adw-toolbar-view>);Wrap Box
Section titled “Wrap Box”Lay children out in a horizontal line and let them wrap onto new lines when they run out of room. This is what you want for a variable-length run of tags, chips or filter pills. Child spacing sets the gap between items on a line, line spacing the gap between the lines.
using Gtk 4.0;using Adw 1;
Adw.WrapBox wrap_box { child-spacing: 8; line-spacing: 8;
Gtk.Button { label: _("Design");
styles ["pill"] }
Gtk.Button { label: _("Adwaita");
styles ["pill"] }
Gtk.Button { label: _("GNOME");
styles ["pill"] }
Gtk.Button { label: _("GTK");
styles ["pill"] }
Gtk.Button { label: _("TypeScript");
styles ["pill"] }
Gtk.Button { label: _("Storybook");
styles ["pill"] }
Gtk.Button { label: _("Wrapping");
styles ["pill"] }
Gtk.Button { label: _("Layout");
styles ["pill"] }}import Gtk from 'gi://Gtk?version=4.0';import { build } from './wrap-box.blp';
const { wrap_box } = build();
const chip = new Gtk.Button({ label: 'Blueprint' });chip.add_css_class('pill');wrap_box.append(chip);wrap_box.childSpacing = 12;<adw-wrap-box id="wrap_box" child-spacing="8" line-spacing="8"> <gtk-button label="Design" class="pill"></gtk-button> <gtk-button label="Adwaita" class="pill"></gtk-button> <gtk-button label="GNOME" class="pill"></gtk-button> <gtk-button label="GTK" class="pill"></gtk-button> <gtk-button label="TypeScript" class="pill"></gtk-button> <gtk-button label="Storybook" class="pill"></gtk-button> <gtk-button label="Wrapping" class="pill"></gtk-button> <gtk-button label="Layout" class="pill"></gtk-button></adw-wrap-box><script type="module"> import '@gjsify/adwaita-web';
const wrap = document.querySelector('#wrap_box');
const chip = document.createElement('gtk-button'); chip.setAttribute('label', 'Blueprint'); chip.classList.add('pill'); wrap.append(chip); wrap.childSpacing = 12;</script>import { mountSharedTree } from '@gjsify/adwaita-web';import tree from './wrap-box.blp?shared-tree';
mountSharedTree(tree);
const wrap = document.querySelector('#wrap_box');
const chip = document.createElement('gtk-button');chip.setAttribute('label', 'Blueprint');chip.classList.add('pill');wrap.append(chip);wrap.childSpacing = 12;<adw:WrapBox xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adw" xmlns:gtk="~/gtk" id="wrap_box" childSpacing="8" lineSpacing="8"> <gtk:Button label="Design" styleClasses="pill" /> <gtk:Button label="Adwaita" styleClasses="pill" /> <gtk:Button label="GNOME" styleClasses="pill" /> <gtk:Button label="GTK" styleClasses="pill" /> <gtk:Button label="TypeScript" styleClasses="pill" /> <gtk:Button label="Storybook" styleClasses="pill" /> <gtk:Button label="Wrapping" styleClasses="pill" /> <gtk:Button label="Layout" styleClasses="pill" /></adw:WrapBox>import { Gtk, type Adw } from '@gjsify/adwaita-nativescript';import { Builder } from '@nativescript/core';
const wrap = Builder.load({ path: '~/views', name: 'wrap-box' }) as Adw.WrapBox;
const chip = new Gtk.Button({ label: 'Blueprint' });chip.add_css_class('pill');wrap.append(chip);wrap.childSpacing = 12;import { Gtk, type Adw } from '@gjsify/adwaita-nativescript';import { build } from '@gjsify/adwaita-nativescript/builder';import tree from './wrap-box.blp?shared-tree';
const wrap = build(tree) as Adw.WrapBox;
const chip = new Gtk.Button({ label: 'Blueprint' });chip.add_css_class('pill');wrap.append(chip);wrap.childSpacing = 12;// mount(() => <AdwWrapBox />, container) — from '@gjsify/gtk-host/solid'const AdwWrapBox = () => ( <adw-wrap-box childSpacing={8} lineSpacing={8} > <gtk-button label="Design" cssClasses={['pill']} /> <gtk-button label="Adwaita" cssClasses={['pill']} /> <gtk-button label="GNOME" cssClasses={['pill']} /> <gtk-button label="GTK" cssClasses={['pill']} /> <gtk-button label="TypeScript" cssClasses={['pill']} /> <gtk-button label="Storybook" cssClasses={['pill']} /> <gtk-button label="Wrapping" cssClasses={['pill']} /> <gtk-button label="Layout" cssClasses={['pill']} /> </adw-wrap-box>);<!-- AdwWrapBox.vue — mount(AdwWrapBox, container) from '@gjsify/gtk-host/vue' --><template> <adw-wrap-box :child-spacing="8" :line-spacing="8" > <gtk-button label="Design" :css-classes="['pill']" /> <gtk-button label="Adwaita" :css-classes="['pill']" /> <gtk-button label="GNOME" :css-classes="['pill']" /> <gtk-button label="GTK" :css-classes="['pill']" /> <gtk-button label="TypeScript" :css-classes="['pill']" /> <gtk-button label="Storybook" :css-classes="['pill']" /> <gtk-button label="Wrapping" :css-classes="['pill']" /> <gtk-button label="Layout" :css-classes="['pill']" /> </adw-wrap-box></template>// createRoot(container).render(<AdwWrapBox />) — from '@gjsify/gtk-host/react'const AdwWrapBox = () => ( <adw-wrap-box childSpacing={8} lineSpacing={8} > <gtk-button label="Design" cssClasses={['pill']} /> <gtk-button label="Adwaita" cssClasses={['pill']} /> <gtk-button label="GNOME" cssClasses={['pill']} /> <gtk-button label="GTK" cssClasses={['pill']} /> <gtk-button label="TypeScript" cssClasses={['pill']} /> <gtk-button label="Storybook" cssClasses={['pill']} /> <gtk-button label="Wrapping" cssClasses={['pill']} /> <gtk-button label="Layout" cssClasses={['pill']} /> </adw-wrap-box>);Breakpoint Bin
Section titled “Breakpoint Bin”Give breakpoints a home without a window. The bin holds breakpoints, each a condition on its own size plus the properties to write while that condition holds, and on every resize it applies the last one that matches — not the narrowest. Its single child is whatever rearranges itself.
The markup form is breakpoints, a JSON array because Adw.Breakpoint is a
GObject rather than a widget and add_breakpoint() is the whole upstream API.
Each setter names a target (a selector for a descendant), an attribute
property and its value; the value the target held when the setter was
registered is what leaving the breakpoint restores.
A bin with breakpoints has no minimum size
Adding a breakpoint makes the bin measure no minimum on either axis, and the
AdwBreakpointBin docs say the stage must set width-request / height-request
for the smallest size you support. The preview does that with inline styles, which
is why the stage under this block is 300px wide: narrow it far enough and the
label swaps.
using Gtk 4.0;using Adw 1;
Adw.BreakpointBin { width-request: 150; height-request: 60;
Gtk.Label caption { label: _("Wide"); ellipsize: end;
styles ["title-1"] }}import Adw from 'gi://Adw?version=1';import Gtk from 'gi://Gtk?version=4.0';
const bin = new Adw.BreakpointBin();bin.set_size_request(150, 60);
const caption = new Gtk.Label({ label: 'Wide', ellipsize: Gtk.EllipsizeMode.END });caption.add_css_class('title-1');bin.set_child(caption);
// A breakpoint writes properties while its condition holds, and the LAST// one that matches wins. Leaving it restores the values the setters// captured when they were added.const narrow = new Adw.Breakpoint(Adw.BreakpointCondition.parse('max-width: 200px'));narrow.add_setter(caption, 'label', 'Narrow');bin.add_breakpoint(narrow);<adw-breakpoint-bin style="min-width: 150px; height: 60px;" breakpoints='[{"condition":"max-width: 200px","setters":[{"target":"#caption","property":"label","value":"Narrow"}]}]'> <gtk-label id="caption" label="Wide" class="title-1"></gtk-label></adw-breakpoint-bin>// mount(() => <AdwBreakpointBin />, container) — from '@gjsify/gtk-host/solid'const AdwBreakpointBin = () => ( <adw-breakpoint-bin> <gtk-label label="Wide" ellipsize="end" cssClasses={['title-1']} /> </adw-breakpoint-bin>);<!-- AdwBreakpointBin.vue — mount(AdwBreakpointBin, container) from '@gjsify/gtk-host/vue' --><template> <adw-breakpoint-bin> <gtk-label label="Wide" ellipsize="end" :css-classes="['title-1']" /> </adw-breakpoint-bin></template>// createRoot(container).render(<AdwBreakpointBin />) — from '@gjsify/gtk-host/react'const AdwBreakpointBin = () => ( <adw-breakpoint-bin> <gtk-label label="Wide" ellipsize="end" cssClasses={['title-1']} /> </adw-breakpoint-bin>);Multi Layout View
Section titled “Multi Layout View”One set of children, several arrangements of them. Every child has an id, every layout has slots with ids, and switching layouts re-inserts each child into the slot of the same id — which is how a secondary pane becomes a sidebar in one arrangement and part of the content in another without the application moving anything. layout-name picks the arrangement; the first one declared is the one in use until you say otherwise.
An Adw.Layout is a GObject, so there is no Adw.Layout tag: here a direct
child carrying name is the layout, a direct child carrying slot is a
child, and an <adw-layout-slot> inside a layout is where that child lands.
Why this block has no framework snippet
A layout is a GObject upstream and each child is paired with a slot by a method call, so neither half is a static tree in any of the dialects — the gallery ledger says so rather than publishing markup that would render nothing. The browser element carries both in markup, which is what the preview above shows.
using Gtk 4.0;using Adw 1;
Adw.MultiLayoutView { layout-name: "wide";
Adw.NavigationSplitView { min-sidebar-width: 160;
Adw.LayoutSlot { id: "secondary"; }
Adw.LayoutSlot { id: "primary"; } }}import Adw from 'gi://Adw?version=1';
const wide = new Adw.NavigationSplitView({ minSidebarWidth: 160 });wide.sidebar = new Adw.LayoutSlot('secondary');wide.content = new Adw.LayoutSlot('primary');
const narrow = new Adw.NavigationSplitView({ collapsed: true, minSidebarWidth: 160 });narrow.sidebar = new Adw.LayoutSlot('secondary');narrow.content = new Adw.LayoutSlot('primary');
const view = new Adw.MultiLayoutView();// The FIRST layout added is the one in use.const first = new Adw.Layout(wide);first.name = 'wide';view.add_layout(first);const second = new Adw.Layout(narrow);second.name = 'narrow';view.add_layout(second);
view.set_child('primary', new Adw.NavigationPage({ title: 'Inbox', tag: 'inbox' }));view.set_child('secondary', new Adw.NavigationPage({ title: 'Message', tag: 'message' }));<adw-multi-layout-view layout-name="wide" style="width: 100%; height: 100%;"> <adw-navigation-split-view name="wide" style="width: 100%; height: 100%;"> <adw-layout-slot id="secondary" slot="sidebar"></adw-layout-slot> <adw-layout-slot id="primary" slot="content"></adw-layout-slot> </adw-navigation-split-view> <adw-navigation-split-view name="narrow" collapsed style="width: 100%; height: 100%;"> <adw-layout-slot id="secondary" slot="content"></adw-layout-slot> <adw-layout-slot id="primary" slot="content"></adw-layout-slot> </adw-navigation-split-view> <adw-navigation-page slot="primary" tag="inbox" title="Inbox"> <gtk-label label="General — 12 unread"></gtk-label> </adw-navigation-page> <adw-navigation-page slot="secondary" tag="message" title="Message"> <gtk-label label="Resizing the window"></gtk-label> </adw-navigation-page></adw-multi-layout-view>Layout Slot
Section titled “Layout Slot”The named hole one layout of a multi-layout view leaves for a child. It takes the child whose id matches its own, and its visibility follows that child’s — so hiding the child takes the hole with it and nothing more. The id is construct-only upstream, which is why it is the one thing this element reads.
A slot on its own is a bin, and that is what the preview shows. To see the pairing
work, use the view above: the two slots in it are what the secondary and
primary children land in.
Duplicate ids across layouts are the widget's own model
The example in the libadwaita documentation puts an AdwLayoutSlot with
id="primary" in both layouts of one view — that is how a child follows a
layout change. The element resolves slots from the layout it is currently
mounting, never by looking an id up in the document, so two slots sharing an id
are ordinary.
using Gtk 4.0;using Adw 1;
Adw.LayoutSlot { id: "primary";
Gtk.Label { label: _("The child the view inserted here."); }}import Adw from 'gi://Adw?version=1';
// A slot only ever exists inside a layout, and a layout inside a view:// the view is what inserts the child whose id matches the slot's.const slot = new Adw.LayoutSlot('primary');const layout = new Adw.Layout(slot);layout.name = 'only';
const view = new Adw.MultiLayoutView();view.add_layout(layout);view.set_child('primary', new Adw.NavigationPage({ title: 'Inbox', tag: 'inbox' }));<adw-layout-slot id="primary"> <gtk-label label="The child the view inserted here."></gtk-label></adw-layout-slot>Related
Section titled “Related”- Boxed Lists: the rows a
Clampkeeps at a readable width. - Navigation: split views and pages that pair with a
HeaderBar. - Feedback: the dialogs that present themselves over the window.
- Native Adwaita Apps: assemble these into a running app.
- Adwaita Storybook: the same widgets in a live component browser you can poke at.