Navigation
These widgets move the user between the top-level places of your app, and they adapt that movement to the window size for free. On a wide window a sidebar and its content sit side by side. As the window narrows, the same structure folds into a stack you push and pop, or into a panel that overlays the content. You build one structure, not two.
Each block below is a stack of windows. The first one RUNS the widget: the preview
is the live browser port, so it follows the light and dark toggle the way a real
window would, and the HTML tab beside it holds the very markup that paints it —
one import '@gjsify/adwaita-web' registers every element in it. Then comes
one window per kind of implementation — Native TypeScript, the @girs program
that runs unchanged on GJS, Node, Bun and Deno, with the matching Blueprint
declaration beside it, UI frameworks, the same widget written in Solid, Vue and
React through @gjsify/gtk-host — or, where the widget is built imperatively rather
than declared, the recorded reason it has no such snippet — and NativeScript,
the port that runs on a phone. That last window splits the way the first one does. An
XML template holds the tree, and the TypeScript beside it is the loader.
Libadwaita is the reference here, typed by
@girs/adw-1.
@gjsify/adwaita-web
and @gjsify/adwaita-nativescript
follow its naming, and where one of them differs, its own window says so.
Navigation Split View
Section titled “Navigation Split View”The workhorse for a mail-client or settings layout: a resizable sidebar beside a content pane. On a wide window the two sit side by side. When the window is narrow the view collapses into a single navigation stack, so selecting a sidebar row pushes the content page and a back button returns to the list. Give the sidebar and the content each their own navigation page and header bar.
<adw-navigation-split-view show-content style="flex: 1;"> <adw-toolbar-view slot="sidebar"> <adw-header-bar slot="top"> <adw-window-title slot="center" title="Mailboxes"></adw-window-title> </adw-header-bar> <adw-sidebar mode="sidebar" selected="0"> <adw-sidebar-section> <adw-sidebar-item title="All Mail" subtitle="128 messages" icon-name="mail-unread-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Starred" subtitle="6 messages" icon-name="starred-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Drafts" subtitle="2 messages" icon-name="document-edit-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Archive" subtitle="512 messages" icon-name="folder-symbolic"></adw-sidebar-item> </adw-sidebar-section> </adw-sidebar> </adw-toolbar-view> <adw-toolbar-view slot="content"> <adw-header-bar slot="top"> <adw-window-title slot="center" title="All Mail"></adw-window-title> </adw-header-bar> <adw-status-page icon="mail-unread-symbolic" title="All Mail" description="Select a conversation from the list to read it here."></adw-status-page> </adw-toolbar-view></adw-navigation-split-view>| Attribute | In this preview |
|---|---|
| collapsed | not used |
| show-content | set |
| sidebar-position | not used |
| min-sidebar-width | not used |
| max-sidebar-width | not used |
| breakpoint | not used |
import Adw from 'gi://Adw?version=1';
// Sidebar: an Adw.Sidebar, NOT a boxed list. A boxed list is a card of// settings and draws itself as one: rounded, bordered, edge to edge in a// pane that gives it no margin. A navigation sidebar is a flat selectable// list, and the widget that already knows how to be one is this.const section = new Adw.SidebarSection();for (const [title, subtitle, icon] of [ ['All Mail', '128 messages', 'mail-unread-symbolic'], ['Starred', '6 messages', 'starred-symbolic'],] as const) { section.append(new Adw.SidebarItem({ title, subtitle, iconName: icon }));}
const sidebarList = new Adw.Sidebar({ mode: Adw.SidebarMode.SIDEBAR, selected: 0 });sidebarList.append(section);
const sidebarToolbar = new Adw.ToolbarView();sidebarToolbar.add_top_bar(new Adw.HeaderBar());sidebarToolbar.content = sidebarList;const sidebar = new Adw.NavigationPage({ title: 'Mailboxes', child: sidebarToolbar });
// Content: a status page inside a toolbar view.const status = new Adw.StatusPage({ iconName: 'mail-unread-symbolic', title: 'All Mail', description: 'Select a conversation from the list to read it here.', vexpand: true,});const contentToolbar = new Adw.ToolbarView();contentToolbar.add_top_bar(new Adw.HeaderBar());contentToolbar.content = status;const content = new Adw.NavigationPage({ title: 'All Mail', child: contentToolbar });
const view = new Adw.NavigationSplitView({ sidebar, content, showContent: true });using Gtk 4.0;using Adw 1;
Adw.NavigationSplitView { sidebar: Adw.NavigationPage { title: _("Mailboxes");
child: Adw.ToolbarView { [top] Adw.HeaderBar {}
content: Adw.Sidebar { mode: sidebar; selected: 0;
Adw.SidebarSection { Adw.SidebarItem { title: _("All Mail"); subtitle: _("128 messages"); icon-name: "mail-unread-symbolic"; }
Adw.SidebarItem { title: _("Starred"); subtitle: _("6 messages"); icon-name: "starred-symbolic"; }
Adw.SidebarItem { title: _("Drafts"); subtitle: _("2 messages"); icon-name: "document-edit-symbolic"; }
Adw.SidebarItem { title: _("Archive"); subtitle: _("512 messages"); icon-name: "folder-symbolic"; } } }; }; };
content: Adw.NavigationPage { title: _("All Mail");
child: Adw.ToolbarView { [top] Adw.HeaderBar {}
content: Adw.StatusPage { icon-name: "mail-unread-symbolic"; title: _("All Mail"); description: _("Select a conversation from the list to read it here."); }; }; };}// mount(() => <AdwNavigationSplitView />, container) — from '@gjsify/gtk-host/solid'const AdwNavigationSplitView = () => ( <adw-navigation-split-view showContent> <adw-navigation-page slot="sidebar" tag="sidebar" title="Mailboxes" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Mailboxes" /> </adw-header-bar> <adw-status-page slot="content" iconName="mail-unread-symbolic" title="Mailboxes" /> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page slot="content" tag="content" title="All Mail" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="All Mail" /> </adw-header-bar> <adw-status-page slot="content" iconName="mail-unread-symbolic" title="All Mail" description="Select a conversation from the list to read it here." /> </adw-toolbar-view> </adw-navigation-page> </adw-navigation-split-view>);<!-- AdwNavigationSplitView.vue — mount(AdwNavigationSplitView, container) from '@gjsify/gtk-host/vue' --><template> <adw-navigation-split-view :show-content="true"> <adw-navigation-page slot="sidebar" tag="sidebar" title="Mailboxes" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Mailboxes" /> </adw-header-bar> <adw-status-page slot="content" icon-name="mail-unread-symbolic" title="Mailboxes" /> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page slot="content" tag="content" title="All Mail" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="All Mail" /> </adw-header-bar> <adw-status-page slot="content" icon-name="mail-unread-symbolic" title="All Mail" description="Select a conversation from the list to read it here." /> </adw-toolbar-view> </adw-navigation-page> </adw-navigation-split-view></template>// createRoot(container).render(<AdwNavigationSplitView />) — from '@gjsify/gtk-host/react'const AdwNavigationSplitView = () => ( <adw-navigation-split-view showContent> <adw-navigation-page slot="sidebar" tag="sidebar" title="Mailboxes" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Mailboxes" /> </adw-header-bar> <adw-status-page slot="content" iconName="mail-unread-symbolic" title="Mailboxes" /> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page slot="content" tag="content" title="All Mail" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="All Mail" /> </adw-header-bar> <adw-status-page slot="content" iconName="mail-unread-symbolic" title="All Mail" description="Select a conversation from the list to read it here." /> </adw-toolbar-view> </adw-navigation-page> </adw-navigation-split-view>);import { AdwHeaderBar, AdwNavigationSplitView, AdwSidebar, AdwStatusPage, AdwToolbarView,} from '@gjsify/adwaita-nativescript';import { mailUnreadSymbolic } from '@gjsify/adwaita-icons/status';
// Sidebar: the navigation list, not a boxed list. The NS surface takes a// flat label list (no per-item subtitle/icon slot).const sidebarList = new AdwSidebar();sidebarList.setItems(['All Mail', 'Starred', 'Drafts', 'Archive']);sidebarList.selected = 0;
const sidebarHeader = new AdwHeaderBar();sidebarHeader.title = 'Mailboxes';const sidebar = new AdwToolbarView();sidebar.addTopBar(sidebarHeader);sidebar.setContent(sidebarList);
// Content: a status page inside a toolbar view.const status = new AdwStatusPage();status.iconName = mailUnreadSymbolic;status.title = 'All Mail';status.description = 'Select a conversation from the list to read it here.';const contentHeader = new AdwHeaderBar();contentHeader.title = 'All Mail';const content = new AdwToolbarView();content.addTopBar(contentHeader);content.setContent(status);
const view = new AdwNavigationSplitView();view.setSidebar(sidebar);view.setContent(content);view.showSidebar = true;<adw:AdwNavigationSplitView xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adwaita"> <adw:AdwNavigationSplitView.sidebar> <adw:AdwToolbarView> <adw:AdwToolbarView.topBar> <adw:AdwHeaderBar title="Mailboxes" /> </adw:AdwToolbarView.topBar> <adw:AdwToolbarView.content> <adw:AdwStatusPage title="Mailboxes" /> </adw:AdwToolbarView.content> </adw:AdwToolbarView> </adw:AdwNavigationSplitView.sidebar> <adw:AdwNavigationSplitView.content> <adw:AdwToolbarView> <adw:AdwToolbarView.topBar> <adw:AdwHeaderBar title="All Mail" /> </adw:AdwToolbarView.topBar> <adw:AdwToolbarView.content> <adw:AdwStatusPage title="All Mail" description="Select a conversation from the list to read it here." /> </adw:AdwToolbarView.content> </adw:AdwToolbarView> </adw:AdwNavigationSplitView.content></adw:AdwNavigationSplitView>Overlay Split View
Section titled “Overlay Split View”Also a sidebar beside the content, but with a different collapsed behaviour: instead of replacing the content, the sidebar slides over the top of it as a temporary panel above a dimming scrim. Choose this one for a utility, filter or navigation pane that should not push the main content aside on a narrow window.
<adw-overlay-split-view show-sidebar style="flex: 1;"> <adw-toolbar-view slot="sidebar"> <adw-header-bar slot="top"></adw-header-bar> <adw-sidebar mode="sidebar" selected="0"> <adw-sidebar-section> <adw-sidebar-item title="Home" icon-name="go-home-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Discover" icon-name="system-search-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Library" icon-name="folder-music-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Settings" icon-name="emblem-system-symbolic"></adw-sidebar-item> </adw-sidebar-section> </adw-sidebar> </adw-toolbar-view> <adw-toolbar-view slot="content"> <adw-header-bar slot="top"></adw-header-bar> <adw-status-page icon="folder-music" title="Your Library" description="Toggle the sidebar to browse sections. Collapse it to overlay the content."></adw-status-page> </adw-toolbar-view></adw-overlay-split-view>| Attribute | In this preview |
|---|---|
| show-sidebar | set |
| collapsed | not used |
| pin-sidebar | not used |
| sidebar-position | not used |
| min-sidebar-width | not used |
| max-sidebar-width | not used |
| sidebar-width-fraction | not used |
| enable-show-gesture | not used |
| enable-hide-gesture | not used |
| breakpoint | not used |
import Adw from 'gi://Adw?version=1';
// Sidebar: a flat, title-less toolbar view over an Adw.Sidebar. The pane// needs no style class of its own: Adw.OverlaySplitView puts `.sidebar-pane`// on it while docked and `.overlay-pane` while collapsed, which is what// gives it its colour. (The old `.sidebar` class libadwaita deprecates in// favour of `.navigation-sidebar`; Adw.Sidebar carries that look itself.)const section = new Adw.SidebarSection();for (const [title, icon] of [ ['Home', 'go-home-symbolic'], ['Discover', 'system-search-symbolic'], ['Library', 'folder-music-symbolic'], ['Settings', 'emblem-system-symbolic'],] as const) { section.append(new Adw.SidebarItem({ title, iconName: icon }));}
const sidebarList = new Adw.Sidebar({ mode: Adw.SidebarMode.SIDEBAR, selected: 0 });sidebarList.append(section);
const sidebar = new Adw.ToolbarView();sidebar.add_top_bar(new Adw.HeaderBar({ showTitle: false }));sidebar.content = sidebarList;
const status = new Adw.StatusPage({ iconName: 'folder-music-symbolic', title: 'Your Library', description: 'Toggle the sidebar to browse sections. Collapse it to overlay the content.', vexpand: true,});const content = new Adw.ToolbarView();content.add_top_bar(new Adw.HeaderBar());content.content = status;
const view = new Adw.OverlaySplitView({ sidebar, content, showSidebar: true, collapsed: false });using Gtk 4.0;using Adw 1;
Adw.OverlaySplitView { show-sidebar: true;
sidebar: Adw.ToolbarView { [top] Adw.HeaderBar {}
content: Adw.Sidebar { mode: sidebar; selected: 0;
Adw.SidebarSection { Adw.SidebarItem { title: _("Home"); icon-name: "go-home-symbolic"; }
Adw.SidebarItem { title: _("Discover"); icon-name: "system-search-symbolic"; }
Adw.SidebarItem { title: _("Library"); icon-name: "folder-music-symbolic"; }
Adw.SidebarItem { title: _("Settings"); icon-name: "emblem-system-symbolic"; } } }; };
content: Adw.ToolbarView { [top] Adw.HeaderBar {}
content: Adw.StatusPage { icon-name: "folder-music-symbolic"; title: _("Your Library"); description: _("Toggle the sidebar to browse sections. Collapse it to overlay the content."); }; };}// mount(() => <AdwOverlaySplitView />, container) — from '@gjsify/gtk-host/solid'const AdwOverlaySplitView = () => ( <adw-overlay-split-view showSidebar> <adw-toolbar-view slot="sidebar"> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Library" /> </adw-header-bar> <adw-status-page slot="content" iconName="folder-music-symbolic" title="Sections" /> </adw-toolbar-view> <adw-toolbar-view slot="content"> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Your Library" /> </adw-header-bar> <adw-status-page slot="content" iconName="folder-music-symbolic" title="Your Library" description="Toggle the sidebar to browse sections." /> </adw-toolbar-view> </adw-overlay-split-view>);<!-- AdwOverlaySplitView.vue — mount(AdwOverlaySplitView, container) from '@gjsify/gtk-host/vue' --><template> <adw-overlay-split-view :show-sidebar="true"> <adw-toolbar-view slot="sidebar"> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Library" /> </adw-header-bar> <adw-status-page slot="content" icon-name="folder-music-symbolic" title="Sections" /> </adw-toolbar-view> <adw-toolbar-view slot="content"> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Your Library" /> </adw-header-bar> <adw-status-page slot="content" icon-name="folder-music-symbolic" title="Your Library" description="Toggle the sidebar to browse sections." /> </adw-toolbar-view> </adw-overlay-split-view></template>// createRoot(container).render(<AdwOverlaySplitView />) — from '@gjsify/gtk-host/react'const AdwOverlaySplitView = () => ( <adw-overlay-split-view showSidebar> <adw-toolbar-view slot="sidebar"> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Library" /> </adw-header-bar> <adw-status-page slot="content" iconName="folder-music-symbolic" title="Sections" /> </adw-toolbar-view> <adw-toolbar-view slot="content"> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Your Library" /> </adw-header-bar> <adw-status-page slot="content" iconName="folder-music-symbolic" title="Your Library" description="Toggle the sidebar to browse sections." /> </adw-toolbar-view> </adw-overlay-split-view>);import { AdwHeaderBar, AdwOverlaySplitView, AdwSidebar, AdwStatusPage, AdwToolbarView,} from '@gjsify/adwaita-nativescript';import { folderMusicSymbolic } from '@gjsify/adwaita-icons/places';
const sidebarList = new AdwSidebar();sidebarList.setItems(['Home', 'Discover', 'Library', 'Settings']);sidebarList.selected = 0;
const header = new AdwHeaderBar();header.flat = true;const sidebar = new AdwToolbarView();sidebar.addTopBar(header);sidebar.setContent(sidebarList);
const status = new AdwStatusPage();status.iconName = folderMusicSymbolic;status.title = 'Your Library';status.description = 'Toggle the sidebar to browse sections. Collapse it to overlay the content.';const content = new AdwToolbarView();content.addTopBar(new AdwHeaderBar());content.setContent(status);
const view = new AdwOverlaySplitView();view.setSidebar(sidebar);view.setContent(content);view.showSidebar = true;view.collapsed = false;<adw:AdwOverlaySplitView xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adwaita"> <adw:AdwOverlaySplitView.sidebar> <adw:AdwToolbarView> <adw:AdwToolbarView.topBar> <adw:AdwHeaderBar title="Library" /> </adw:AdwToolbarView.topBar> <adw:AdwToolbarView.content> <adw:AdwStatusPage title="Sections" /> </adw:AdwToolbarView.content> </adw:AdwToolbarView> </adw:AdwOverlaySplitView.sidebar> <adw:AdwOverlaySplitView.content> <adw:AdwToolbarView> <adw:AdwToolbarView.topBar> <adw:AdwHeaderBar title="Your Library" /> </adw:AdwToolbarView.topBar> <adw:AdwToolbarView.content> <adw:AdwStatusPage title="Your Library" description="Toggle the sidebar to browse sections." /> </adw:AdwToolbarView.content> </adw:AdwToolbarView> </adw:AdwOverlaySplitView.content></adw:AdwOverlaySplitView>Navigation View
Section titled “Navigation View”A navigation stack: push a page, pop it, and get the back button for free. The root page shows first. Pushing slides the next page in from the trailing edge when transitions are animated, and a back-swipe gesture takes the user back.
The preview below is interactive: click Open contact on the root page to push the detail page and watch the back button appear.
<adw-navigation-view animate-transitions="true" style="flex: 1;"> <adw-navigation-page tag="root" title="Contacts"> <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="center" title="Contacts"></adw-window-title> </adw-header-bar> <!-- The toolbar view gives its content widget the whole rect, as Adw.ToolbarView does, so a button that wants to sit in the middle says so. This box is the web spelling of the TypeScript tab's halign/valign: center. --> <div style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"> <gtk-button label="Open contact" pill suggested></gtk-button> </div> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page tag="detail" title="Ada Lovelace"> <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="center" title="Ada Lovelace"></adw-window-title> </adw-header-bar> <adw-status-page icon="avatar-default" title="Ada Lovelace" description="Mathematician and writer, the first computer programmer."></adw-status-page> </adw-toolbar-view> </adw-navigation-page></adw-navigation-view>| Attribute | In this preview |
|---|---|
| animate-transitions | true |
| pop-on-escape | not used |
import Adw from 'gi://Adw?version=1';import Gtk from 'gi://Gtk?version=4.0';
const page = (title: string, body: Gtk.Widget) => { const toolbar = new Adw.ToolbarView(); toolbar.add_top_bar(new Adw.HeaderBar()); toolbar.content = body; return new Adw.NavigationPage({ title, child: toolbar });};
const view = new Adw.NavigationView({ animateTransitions: true });
const detail = page('Ada Lovelace', new Adw.StatusPage({ iconName: 'avatar-default-symbolic', title: 'Ada Lovelace', description: 'Mathematician and writer, the first computer programmer.', vexpand: true,}));
const open = new Gtk.Button({ label: 'Open contact', halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, vexpand: true,});open.add_css_class('pill');open.add_css_class('suggested-action');open.connect('clicked', () => view.push(detail));
view.add(page('Contacts', open));view.add(detail);using Gtk 4.0;using Adw 1;
Adw.NavigationView { Adw.NavigationPage { tag: "root"; title: _("Contacts");
child: Adw.ToolbarView { [top] Adw.HeaderBar {}
content: Gtk.Button { label: _("Open contact"); halign: center; valign: center; action-name: "navigation.push"; action-target: "'detail'";
styles ["pill", "suggested-action"] }; }; }
Adw.NavigationPage { tag: "detail"; title: _("Ada Lovelace");
child: Adw.ToolbarView { [top] Adw.HeaderBar {}
content: Adw.StatusPage { icon-name: "avatar-default-symbolic"; title: _("Ada Lovelace"); description: _("Mathematician and writer, the first computer programmer."); }; }; }}// mount(() => <AdwNavigationView />, container) — from '@gjsify/gtk-host/solid'const AdwNavigationView = () => ( <adw-navigation-view> <adw-navigation-page tag="root" title="Contacts" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Contacts" /> </adw-header-bar> <adw-status-page slot="content" title="Contacts" description="Push a page to see the transition." /> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page tag="detail" title="Ada Lovelace" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Ada Lovelace" /> </adw-header-bar> <adw-status-page slot="content" iconName="avatar-default-symbolic" title="Ada Lovelace" description="Mathematician and writer, the first computer programmer." /> </adw-toolbar-view> </adw-navigation-page> </adw-navigation-view>);<!-- AdwNavigationView.vue — mount(AdwNavigationView, container) from '@gjsify/gtk-host/vue' --><template> <adw-navigation-view> <adw-navigation-page tag="root" title="Contacts" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Contacts" /> </adw-header-bar> <adw-status-page slot="content" title="Contacts" description="Push a page to see the transition." /> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page tag="detail" title="Ada Lovelace" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Ada Lovelace" /> </adw-header-bar> <adw-status-page slot="content" icon-name="avatar-default-symbolic" title="Ada Lovelace" description="Mathematician and writer, the first computer programmer." /> </adw-toolbar-view> </adw-navigation-page> </adw-navigation-view></template>// createRoot(container).render(<AdwNavigationView />) — from '@gjsify/gtk-host/react'const AdwNavigationView = () => ( <adw-navigation-view> <adw-navigation-page tag="root" title="Contacts" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Contacts" /> </adw-header-bar> <adw-status-page slot="content" title="Contacts" description="Push a page to see the transition." /> </adw-toolbar-view> </adw-navigation-page> <adw-navigation-page tag="detail" title="Ada Lovelace" > <adw-toolbar-view> <adw-header-bar slot="top"> <adw-window-title slot="title" title="Ada Lovelace" /> </adw-header-bar> <adw-status-page slot="content" iconName="avatar-default-symbolic" title="Ada Lovelace" description="Mathematician and writer, the first computer programmer." /> </adw-toolbar-view> </adw-navigation-page> </adw-navigation-view>);import { GtkButton, AdwHeaderBar, AdwNavigationView, AdwStatusPage, AdwToolbarView } from '@gjsify/adwaita-nativescript';import { avatarDefaultSymbolic } from '@gjsify/adwaita-icons/status';
const view = new AdwNavigationView();
// Detail page.const detailHeader = new AdwHeaderBar();detailHeader.title = 'Ada Lovelace';const detailStatus = new AdwStatusPage();detailStatus.iconName = avatarDefaultSymbolic;detailStatus.title = 'Ada Lovelace';detailStatus.description = 'Mathematician and writer, the first computer programmer.';const detail = new AdwToolbarView();detail.addTopBar(detailHeader);detail.setContent(detailStatus);
// Root page: a pill button that pushes the detail page by tag.const rootHeader = new AdwHeaderBar();rootHeader.title = 'Contacts';const open = new GtkButton();open.text = 'Open contact';open.variant = 'pill';open.addEventListener('tap', () => view.push('detail'));const root = new AdwToolbarView();root.addTopBar(rootHeader);root.setContent(open);
view.add(root, 'root');view.add(detail, 'detail');<adw:AdwNavigationView xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adwaita"> <adw:AdwToolbarView> <adw:AdwToolbarView.topBar> <adw:AdwHeaderBar title="Contacts" /> </adw:AdwToolbarView.topBar> <adw:AdwToolbarView.content> <adw:AdwStatusPage title="Contacts" description="Push a page to see the transition." /> </adw:AdwToolbarView.content> </adw:AdwToolbarView> <adw:AdwToolbarView> <adw:AdwToolbarView.topBar> <adw:AdwHeaderBar title="Ada Lovelace" /> </adw:AdwToolbarView.topBar> <adw:AdwToolbarView.content> <adw:AdwStatusPage title="Ada Lovelace" description="Mathematician and writer, the first computer programmer." /> </adw:AdwToolbarView.content> </adw:AdwToolbarView></adw:AdwNavigationView>Sidebar
Section titled “Sidebar”The list that goes inside a split view: selectable items grouped into titled
sections, where selecting one drives the content beside it. Each item takes a
title, an optional subtitle and a leading icon. Two modes let it adapt: a
persistent sidebar for wide layouts and a full-width page for narrow ones.
Use this rather than a boxed list. A boxed list draws itself as a settings card, which looks wrong edge to edge in a pane; a sidebar is a flat selectable list and already knows how to be one.
<adw-sidebar mode="sidebar" selected="0" style="width: 220px;"> <adw-sidebar-section title="Mailboxes"> <adw-sidebar-item title="Inbox" subtitle="3 unread" icon-name="mail-unread-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Starred" subtitle="Favourites" icon-name="starred-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Sent" subtitle="Outgoing mail" icon-name="mail-send-symbolic"></adw-sidebar-item> <adw-sidebar-item title="Archive" subtitle="Older mail" icon-name="folder-symbolic"></adw-sidebar-item> </adw-sidebar-section></adw-sidebar><div style="width: 1px; align-self: stretch; background-color: var(--separator-color, var(--card-shade-color));"></div><adw-status-page title="Inbox" icon="mail-unread" description="3 unread" style="flex: 1 1 auto;"></adw-status-page>| Attribute | In this preview |
|---|---|
| mode | sidebar |
| selected | 0 |
import Adw from 'gi://Adw?version=1';import Gtk from 'gi://Gtk?version=4.0';
const items = [ { title: 'Inbox', subtitle: '3 unread', icon: 'mail-unread-symbolic' }, { title: 'Starred', subtitle: 'Favourites', icon: 'starred-symbolic' }, { title: 'Sent', subtitle: 'Outgoing mail', icon: 'mail-send-symbolic' }, { title: 'Archive', subtitle: 'Older mail', icon: 'folder-symbolic' },];
const section = new Adw.SidebarSection({ title: 'Mailboxes' });for (const it of items) { section.append(new Adw.SidebarItem({ title: it.title, subtitle: it.subtitle, iconName: it.icon }));}
const sidebar = new Adw.Sidebar({ mode: Adw.SidebarMode.SIDEBAR, selected: 0, widthRequest: 220 });sidebar.append(section);
const status = new Adw.StatusPage({ hexpand: true, vexpand: true });const syncContent = () => { const it = items[sidebar.selected] ?? items[0]; status.title = it.title; status.iconName = it.icon; status.description = it.subtitle;};sidebar.connect('notify::selected', syncContent);syncContent();
const box = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL });box.append(sidebar);box.append(new Gtk.Separator({ orientation: Gtk.Orientation.VERTICAL }));box.append(status);using Gtk 4.0;using Adw 1;
Adw.Sidebar { mode: sidebar; selected: 0;
Adw.SidebarSection { title: _("Mailboxes");
Adw.SidebarItem { title: _("Inbox"); subtitle: _("3 unread"); icon-name: "mail-unread-symbolic"; }
Adw.SidebarItem { title: _("Starred"); subtitle: _("Favourites"); icon-name: "starred-symbolic"; }
Adw.SidebarItem { title: _("Sent"); subtitle: _("Outgoing mail"); icon-name: "mail-send-symbolic"; }
Adw.SidebarItem { title: _("Archive"); subtitle: _("Older mail"); icon-name: "folder-symbolic"; } }}
No Solid, Vue or React snippet for Adw.Sidebar: uncurated-placement — and its items are AdwSidebarItem GObjects, which have no tag either.
Every snippet in this gallery was compiled and run before it was written down. Where a widget is built imperatively rather than declared — a Gio.MenuModel, a dialog opened with present(), a container with no child policy — there is nothing for the three dialects to differ about, so the reason is recorded here rather than the tab being quietly absent.
import { AdwSidebar, AdwStatusPage, NOTIFY_SIDEBAR_SELECTED } from '@gjsify/adwaita-nativescript';import { mailUnreadSymbolic, starredSymbolic } from '@gjsify/adwaita-icons/status';import { mailSendSymbolic } from '@gjsify/adwaita-icons/actions';import { folderSymbolic } from '@gjsify/adwaita-icons/places';import { GridLayout, ItemSpec } from '@nativescript/core';
const items = [ { title: 'Inbox', subtitle: '3 unread', icon: mailUnreadSymbolic }, { title: 'Starred', subtitle: 'Favourites', icon: starredSymbolic }, { title: 'Sent', subtitle: 'Outgoing mail', icon: mailSendSymbolic }, { title: 'Archive', subtitle: 'Older mail', icon: folderSymbolic },];
// The NS surface takes a flat label list (no per-item subtitle/icon slot).const sidebar = new AdwSidebar();sidebar.setItems(items.map((it) => it.title));sidebar.selected = 0;sidebar.width = 220;
const status = new AdwStatusPage();const syncContent = () => { const it = items[sidebar.selected] ?? items[0]; status.iconName = it.icon; status.title = it.title; status.description = it.subtitle;};sidebar.addEventListener(NOTIFY_SIDEBAR_SELECTED, syncContent);syncContent();
const box = new GridLayout();box.addColumn(new ItemSpec(1, 'auto'));box.addColumn(new ItemSpec(1, 'star'));GridLayout.setColumn(sidebar, 0);box.addChild(sidebar);GridLayout.setColumn(status, 1);box.addChild(status);
No NativeScript XML template for Adw.Sidebar: AdwSidebar.items and .sections are arrays of item descriptors; an XML attribute is a string.
NativeScript’s Builder reaches a widget through an attribute, which is always a string, and through a child, which lands only where the widget declares a slot for it. A property that is an array of options or a reference to another view fits through neither, so the TypeScript beside this window is where that widget is built.
Bottom Sheet
Section titled “Bottom Sheet”A sheet that slides up from the bottom edge over the content it belongs to,
which suits contextual actions and secondary content on a narrow window. Make it
modal to dim the content behind it. canClose is on by default, so Escape or
a click on the dimmed area dismisses the sheet; switch it off to keep the sheet
up until your own code closes it. The preview starts with the sheet up: click the
dimmed area to dismiss it. Bringing it back is a property set (open = true), which
this static preview does not wire — the button under the sheet is there to show the
content pane, not to work.
<adw-bottom-sheet open modal can-close style="flex: 1;"> <adw-bottom-sheet-content> <div style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"> <gtk-button pill suggested>Toggle sheet</gtk-button> </div> </adw-bottom-sheet-content> <adw-bottom-sheet-sheet> <div style="display: flex; flex-direction: column; gap: 12px; margin: 18px 18px 24px;"> <span style="font-size: 15pt; font-weight: 800;">Share track</span> <adw-preferences-group> <adw-action-row title="Copy link" activatable> <span slot="prefix" class="adw-icon adw-icon--edit-copy"></span> </adw-action-row> <adw-action-row title="Send to a friend" activatable> <span slot="prefix" class="adw-icon adw-icon--mail-send"></span> </adw-action-row> <adw-action-row title="Add to playlist" activatable> <span slot="prefix" class="adw-icon adw-icon--list-add"></span> </adw-action-row> </adw-preferences-group> </div> </adw-bottom-sheet-sheet></adw-bottom-sheet>| Attribute | In this preview |
|---|---|
| open | set |
| modal | set |
| can-close | set |
| show-drag-handle | not used |
import Adw from 'gi://Adw?version=1';import Gtk from 'gi://Gtk?version=4.0';
const sheet = new Adw.BottomSheet({ open: true, modal: true, canClose: true });
// Persistent content: a centered pill button that toggles the sheet.const toggle = new Gtk.Button({ label: 'Toggle sheet', halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, vexpand: true,});toggle.add_css_class('pill');toggle.add_css_class('suggested-action');toggle.connect('clicked', () => { sheet.open = !sheet.open; });sheet.content = toggle;
// The sheet: a title above a boxed list of share actions.const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 12, marginTop: 18, marginBottom: 24, marginStart: 18, marginEnd: 18,});box.append(new Gtk.Label({ label: 'Share track', halign: Gtk.Align.START, cssClasses: ['title-2'] }));
const group = new Adw.PreferencesGroup();for (const [title, icon] of [ ['Copy link', 'edit-copy-symbolic'], ['Send to a friend', 'mail-send-symbolic'], ['Add to playlist', 'list-add-symbolic'],] as const) { const row = new Adw.ActionRow({ title }); row.add_prefix(new Gtk.Image({ iconName: icon })); row.activatable = true; group.add(row);}box.append(group);sheet.sheet = box;using Gtk 4.0;using Adw 1;
Adw.BottomSheet { open: true; modal: true; can-close: true;
content: Gtk.Button { label: _("Toggle sheet"); halign: center; valign: center;
styles ["pill", "suggested-action"] };
sheet: Gtk.Box { orientation: vertical; spacing: 12; margin-start: 18; margin-end: 18; margin-top: 18; margin-bottom: 24;
Gtk.Label { label: _("Share track"); xalign: 0;
styles ["title-2"] }
Adw.PreferencesGroup { Adw.ActionRow { title: _("Copy link"); activatable: true;
[prefix] Gtk.Image { icon-name: "edit-copy-symbolic"; } }
Adw.ActionRow { title: _("Send to a friend"); activatable: true;
[prefix] Gtk.Image { icon-name: "mail-send-symbolic"; } }
Adw.ActionRow { title: _("Add to playlist"); activatable: true;
[prefix] Gtk.Image { icon-name: "list-add-symbolic"; } } } };}
No Solid, Vue or React snippet for Adw.BottomSheet: uncurated-placement: no child policy for the sheet or the content.
Every snippet in this gallery was compiled and run before it was written down. Where a widget is built imperatively rather than declared — a Gio.MenuModel, a dialog opened with present(), a container with no child policy — there is nothing for the three dialects to differ about, so the reason is recorded here rather than the tab being quietly absent.
import { AdwActionRow, AdwBottomSheet, GtkButton, AdwPreferencesGroup } from '@gjsify/adwaita-nativescript';import { Label, StackLayout } from '@nativescript/core';
const sheet = new AdwBottomSheet();
// Persistent content: a centered pill button that toggles the sheet.const center = new StackLayout();center.horizontalAlignment = 'center';center.verticalAlignment = 'middle';const toggle = new GtkButton();toggle.text = 'Toggle sheet';toggle.variant = 'pill';toggle.addEventListener('tap', () => { sheet.openState = !sheet.openState; });center.addChild(toggle);sheet.setContent(center);
// The sheet: a title above a boxed list of share actions.const box = new StackLayout();box.orientation = 'vertical';const title = new Label();title.text = 'Share track';title.className = 'title-2';box.addChild(title);
const group = new AdwPreferencesGroup();for (const rowTitle of ['Copy link', 'Send to a friend', 'Add to playlist']) { const row = new AdwActionRow(); row.title = rowTitle; group.addRow(row);}box.addChild(group);sheet.setSheet(box);
sheet.openState = true;<adw:AdwBottomSheet xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:adw="~/adwaita" openState="true"> <adw:AdwBottomSheet.content> <adw:AdwStatusPage title="Now Playing" description="The sheet slides up over this." /> </adw:AdwBottomSheet.content> <adw:AdwBottomSheet.sheet> <adw:AdwPreferencesGroup title="Queue"> <adw:AdwActionRow title="Blue Monday" subtitle="New Order" /> <adw:AdwActionRow title="Just Like Heaven" subtitle="The Cure" /> </adw:AdwPreferencesGroup> </adw:AdwBottomSheet.sheet></adw:AdwBottomSheet>Related
Section titled “Related”- View Switching: for switching between peer views instead of moving through a stack.
- Layout: the
ClampandToolbarViewthese navigation surfaces are built on. - Native Adwaita Apps: a ready-made
NavigationSplitViewshell you can drop views into. - Adwaita Storybook: the same widgets in a live component browser you can poke at.