Navigation
Navigation widgets move between the top-level places of an app and adapt that movement to the window size. 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 a panel that overlays the content. Each widget below is described independently of any one toolkit, then shown three ways.
Native GJS (real Libadwaita through @girs/adw-1)
is the primary implementation; the Web (@gjsify/adwaita-web)
and NativeScript (@gjsify/adwaita-nativescript)
ports mirror it.
Navigation Split View
Section titled “Navigation Split View”A resizable sidebar beside a content pane. On a wide window the two panes sit side by side; when the window is narrow the view collapses into a single navigation stack, where selecting a sidebar row pushes the content page and a back button returns to the list. The sidebar and content are each a navigation page with its own header bar.
import Adw from '@girs/adw-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."); }; }; };}<!-- import '@gjsify/adwaita-web' once to register the elements --><div style="height: 340px; width: 100%; display: flex; overflow: hidden;"> <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></div>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.icon = 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;Overlay Split View
Section titled “Overlay Split View”A sidebar shown beside the content, or laid over the top of it when collapsed. Unlike the navigation split view, the collapsed sidebar overlays the content as a temporary panel — sliding in over a dimming scrim — rather than replacing it. This suits a utility, filter or navigation pane that should not push the primary content aside on a narrow window.
import Adw from '@girs/adw-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."); }; };}<div style="height: 340px; width: 100%; display: flex; overflow: hidden;"> <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></div>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.icon = 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;Navigation View
Section titled “Navigation View”A navigation stack of pages with push/pop semantics and an automatic back button. The root page is shown first; pushing a page slides it in from the trailing edge (when transitions are animated) and adds a back button that pops it, while a back-swipe gesture returns to the previous page. In the preview below the root page’s Open contact button pushes the detail page — the push is interactive, so click it to see the transition and back button.
import Adw from '@girs/adw-1';import Gtk from '@girs/gtk-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."); }; }; }}<div style="height: 340px; width: 100%; display: flex; overflow: hidden;"> <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> <adw-button label="Open contact" pill suggested class="adw-navigation-view-root-action"></adw-button> </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></div>import { AdwButton, 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.icon = 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 AdwButton();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');Sidebar
Section titled “Sidebar”A selectable list of items grouped into titled sections. Selecting an item is
the navigation gesture that drives the content shown beside it. Each item carries
a title, an optional subtitle and a leading icon, and the sidebar supports two
modes — a persistent sidebar and a full-width page — so it can adapt between
wide and narrow layouts.
import Adw from '@girs/adw-1';import Gtk from '@girs/gtk-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"; } }}<div style="height: 340px; width: 100%; display: flex; overflow: hidden; border: 1px solid var(--card-shade-color); border-radius: var(--card-radius, 12px);"> <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></div>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.icon = 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);Bottom Sheet
Section titled “Bottom Sheet”A sheet that slides up from the bottom edge over the content it belongs to. It suits contextual actions and secondary content, especially on narrow windows. It can be modal — dimming the content behind it — and optionally dismissed by dragging it down. The preview shows the sheet in its open state; toggling it is interactive in a real window.
import Adw from '@girs/adw-1';import Gtk from '@girs/gtk-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"; } } } };}<div style="height: 340px; width: 100%; display: flex; overflow: hidden; position: relative;"> <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%;"> <adw-button pill suggested>Toggle sheet</adw-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></div>import { AdwActionRow, AdwBottomSheet, AdwButton, 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 AdwButton();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;Related
Section titled “Related”- View Switching — switch between peer views (view stacks, view switchers, tab views) rather than moving through a stack.
- Layout — the
Clamp,ToolbarViewand breakpoints these navigation surfaces are built on. - Adwaita Storybook — the same widgets in a live, interactive component browser.