Skip to content

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.

What the preview does not show

These are the widgets that lean hardest on Libadwaita: adaptive breakpoints, gesture-driven overlays, animated push and pop, back-swipe. The previews run the browser port, so each shows one representative state (sidebar shown, sheet open) and the collapse, overlay and slide interactions are simplified.

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.

Adw.NavigationSplitView
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.NavigationSplitView split_view {
sidebar: Adw.NavigationPage {
title: _("Mailboxes");
child: Adw.ToolbarView {
[top]
Adw.HeaderBar {}
content: Adw.Sidebar 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 status {
icon-name: "mail-unread-symbolic";
title: _("All Mail");
description: _("Select a conversation from the list to read it here.");
};
};
};
}
TypeScript
TypeScript
import { build } from './navigation-split-view.blp';
const { split_view, sidebar, status } = build();
sidebar.connect('activated', () => {
status.title = sidebar.selectedItem?.title ?? '';
split_view.showContent = true;
});
Web Components
index.html
HTML
<adw-navigation-split-view id="split_view">
<adw-navigation-page title="Mailboxes" slot="sidebar">
<adw-toolbar-view slot="child">
<adw-header-bar slot="top"></adw-header-bar>
<adw-sidebar id="sidebar" mode="sidebar" selected="0" slot="content">
<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-navigation-page>
<adw-navigation-page title="All Mail" slot="content">
<adw-toolbar-view slot="child">
<adw-header-bar slot="top"></adw-header-bar>
<adw-status-page
id="status"
icon-name="mail-unread-symbolic"
title="All Mail"
description="Select a conversation from the list to read it here."
slot="content"
></adw-status-page>
</adw-toolbar-view>
</adw-navigation-page>
</adw-navigation-split-view>
<script type="module">
import '@gjsify/adwaita-web';
const splitView = document.querySelector('#split_view');
const sidebar = document.querySelector('#sidebar');
const status = document.querySelector('#status');
sidebar.addEventListener('activated', () => {
status.setAttribute('title', sidebar.selectedItem?.title ?? '');
splitView.setAttribute('show-content', '');
});
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './navigation-split-view.blp?shared-tree';
mountSharedTree(tree);
const splitView = document.querySelector('#split_view');
const sidebar = document.querySelector('#sidebar');
const status = document.querySelector('#status');
sidebar.addEventListener('activated', () => {
status.setAttribute('title', sidebar.selectedItem?.title ?? '');
splitView.setAttribute('show-content', '');
});
NativeScript
views/navigation-split-view.xml
XML
<adw:NavigationSplitView
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
id="split_view"
>
<adw:NavigationSplitView.sidebar>
<adw:NavigationPage title="Mailboxes">
<adw:NavigationPage.child>
<adw:ToolbarView>
<adw:ToolbarView.top>
<adw:HeaderBar />
</adw:ToolbarView.top>
<adw:ToolbarView.content>
<adw:Sidebar id="sidebar" mode="sidebar" selected="0">
<adw:SidebarSection>
<adw:SidebarItem
title="All Mail"
subtitle="128 messages"
iconName="mail-unread-symbolic"
/>
<adw:SidebarItem
title="Starred"
subtitle="6 messages"
iconName="starred-symbolic"
/>
<adw:SidebarItem
title="Drafts"
subtitle="2 messages"
iconName="document-edit-symbolic"
/>
<adw:SidebarItem
title="Archive"
subtitle="512 messages"
iconName="folder-symbolic"
/>
</adw:SidebarSection>
</adw:Sidebar>
</adw:ToolbarView.content>
</adw:ToolbarView>
</adw:NavigationPage.child>
</adw:NavigationPage>
</adw:NavigationSplitView.sidebar>
<adw:NavigationSplitView.content>
<adw:NavigationPage title="All Mail">
<adw:NavigationPage.child>
<adw:ToolbarView>
<adw:ToolbarView.top>
<adw:HeaderBar />
</adw:ToolbarView.top>
<adw:ToolbarView.content>
<adw:StatusPage
id="status"
iconName="mail-unread-symbolic"
title="All Mail"
description="Select a conversation from the list to read it here."
/>
</adw:ToolbarView.content>
</adw:ToolbarView>
</adw:NavigationPage.child>
</adw:NavigationPage>
</adw:NavigationSplitView.content>
</adw:NavigationSplitView>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const splitView = Builder.load({ path: '~/views', name: 'navigation-split-view' }) as Adw.NavigationSplitView;
const sidebar = splitView.getViewById<Adw.Sidebar>('sidebar')!;
const status = splitView.getViewById<Adw.StatusPage>('status')!;
sidebar.connect('activated', () => {
status.title = sidebar.selectedItem?.title ?? '';
splitView.showContent = true;
});
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './navigation-split-view.blp?shared-tree';
const splitView = build(tree) as Adw.NavigationSplitView;
const sidebar = splitView.getViewById<Adw.Sidebar>('sidebar')!;
const status = splitView.getViewById<Adw.StatusPage>('status')!;
sidebar.connect('activated', () => {
status.title = sidebar.selectedItem?.title ?? '';
splitView.showContent = true;
});
Solid
TypeScript
// 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>
);
Vue
Vue
<!-- 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>
React
TypeScript
// 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>
);

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 it for a utility, filter or navigation pane that should not push the main content aside on a narrow window.

Adw.OverlaySplitView
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.OverlaySplitView split_view {
show-sidebar: true;
sidebar: Adw.ToolbarView {
[top]
Adw.HeaderBar {}
content: Adw.Sidebar 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 status {
icon-name: "folder-music-symbolic";
title: _("Your Library");
description: _("Toggle the sidebar to browse sections. Collapse it to overlay the content.");
};
};
}
TypeScript
TypeScript
import { build } from './overlay-split-view.blp';
const { split_view, sidebar } = build();
// Collapsed, the sidebar covers the content, so choosing a section closes it.
sidebar.connect('activated', () => {
if (split_view.collapsed) split_view.showSidebar = false;
});
Web Components
index.html
HTML
<adw-overlay-split-view id="split_view" show-sidebar>
<adw-toolbar-view slot="sidebar">
<adw-header-bar slot="top"></adw-header-bar>
<adw-sidebar id="sidebar" mode="sidebar" selected="0" slot="content">
<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
id="status"
icon-name="folder-music-symbolic"
title="Your Library"
description="Toggle the sidebar to browse sections. Collapse it to overlay the content."
slot="content"
></adw-status-page>
</adw-toolbar-view>
</adw-overlay-split-view>
<script type="module">
import '@gjsify/adwaita-web';
const splitView = document.querySelector('#split_view');
const sidebar = document.querySelector('#sidebar');
sidebar.addEventListener('activated', () => {
if (splitView.collapsed) splitView.showSidebar = false;
});
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './overlay-split-view.blp?shared-tree';
mountSharedTree(tree);
const splitView = document.querySelector('#split_view');
const sidebar = document.querySelector('#sidebar');
sidebar.addEventListener('activated', () => {
if (splitView.collapsed) splitView.showSidebar = false;
});
NativeScript
views/overlay-split-view.xml
XML
<adw:OverlaySplitView
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
id="split_view"
showSidebar="true"
>
<adw:OverlaySplitView.sidebar>
<adw:ToolbarView>
<adw:ToolbarView.top>
<adw:HeaderBar />
</adw:ToolbarView.top>
<adw:ToolbarView.content>
<adw:Sidebar id="sidebar" mode="sidebar" selected="0">
<adw:SidebarSection>
<adw:SidebarItem title="Home" iconName="go-home-symbolic" />
<adw:SidebarItem
title="Discover"
iconName="system-search-symbolic"
/>
<adw:SidebarItem title="Library" iconName="folder-music-symbolic" />
<adw:SidebarItem
title="Settings"
iconName="emblem-system-symbolic"
/>
</adw:SidebarSection>
</adw:Sidebar>
</adw:ToolbarView.content>
</adw:ToolbarView>
</adw:OverlaySplitView.sidebar>
<adw:OverlaySplitView.content>
<adw:ToolbarView>
<adw:ToolbarView.top>
<adw:HeaderBar />
</adw:ToolbarView.top>
<adw:ToolbarView.content>
<adw:StatusPage
id="status"
iconName="folder-music-symbolic"
title="Your Library"
description="Toggle the sidebar to browse sections. Collapse it to overlay the content."
/>
</adw:ToolbarView.content>
</adw:ToolbarView>
</adw:OverlaySplitView.content>
</adw:OverlaySplitView>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const splitView = Builder.load({ path: '~/views', name: 'overlay-split-view' }) as Adw.OverlaySplitView;
const sidebar = splitView.getViewById<Adw.Sidebar>('sidebar')!;
sidebar.connect('activated', () => {
if (splitView.collapsed) splitView.showSidebar = false;
});
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './overlay-split-view.blp?shared-tree';
const splitView = build(tree) as Adw.OverlaySplitView;
const sidebar = splitView.getViewById<Adw.Sidebar>('sidebar')!;
sidebar.connect('activated', () => {
if (splitView.collapsed) splitView.showSidebar = false;
});
Solid
TypeScript
// 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>
);
Vue
Vue
<!-- 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>
React
TypeScript
// 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>
);

A navigation stack: push a page, pop it, and get the back button for free. Pushing slides the next page in from the trailing edge when transitions are animated, and a back-swipe gesture takes the user back.

Pushing a page by its tag

A page carries a tag, and the code below pushes the detail page by its tag when Open contact is clicked. The preview builds the file alone, without that code, so it shows the root page.

Adw.NavigationView
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.NavigationView navigation_view {
Adw.NavigationPage {
tag: "root";
title: _("Contacts");
child: Adw.ToolbarView {
[top]
Adw.HeaderBar {}
content: Gtk.Button open_button {
label: _("Open contact");
halign: center;
valign: center;
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.");
};
};
}
}
TypeScript
TypeScript
import { build } from './navigation-view.blp';
const { navigation_view, open_button } = build();
open_button.connect('clicked', () => navigation_view.push_by_tag('detail'));
Web Components
index.html
HTML
<adw-navigation-view id="navigation_view">
<adw-navigation-page tag="root" title="Contacts">
<adw-toolbar-view slot="child">
<adw-header-bar slot="top"></adw-header-bar>
<gtk-button
id="open_button"
label="Open contact"
halign="center"
valign="center"
class="pill suggested-action"
slot="content"
></gtk-button>
</adw-toolbar-view>
</adw-navigation-page>
<adw-navigation-page tag="detail" title="Ada Lovelace">
<adw-toolbar-view slot="child">
<adw-header-bar slot="top"></adw-header-bar>
<adw-status-page
icon-name="avatar-default-symbolic"
title="Ada Lovelace"
description="Mathematician and writer, the first computer programmer."
slot="content"
></adw-status-page>
</adw-toolbar-view>
</adw-navigation-page>
</adw-navigation-view>
<script type="module">
import '@gjsify/adwaita-web';
const view = document.querySelector('#navigation_view');
const open = document.querySelector('#open_button');
open.addEventListener('click', () => view.pushByTag('detail'));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './navigation-view.blp?shared-tree';
mountSharedTree(tree);
const view = document.querySelector('#navigation_view');
const open = document.querySelector('#open_button');
open.addEventListener('click', () => view.pushByTag('detail'));
NativeScript
views/navigation-view.xml
XML
<adw:NavigationView
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
id="navigation_view"
>
<adw:NavigationPage tag="root" title="Contacts">
<adw:NavigationPage.child>
<adw:ToolbarView>
<adw:ToolbarView.top>
<adw:HeaderBar />
</adw:ToolbarView.top>
<adw:ToolbarView.content>
<gtk:Button
id="open_button"
label="Open contact"
halign="center"
valign="center"
styleClasses="pill suggested-action"
/>
</adw:ToolbarView.content>
</adw:ToolbarView>
</adw:NavigationPage.child>
</adw:NavigationPage>
<adw:NavigationPage tag="detail" title="Ada Lovelace">
<adw:NavigationPage.child>
<adw:ToolbarView>
<adw:ToolbarView.top>
<adw:HeaderBar />
</adw:ToolbarView.top>
<adw:ToolbarView.content>
<adw:StatusPage
iconName="avatar-default-symbolic"
title="Ada Lovelace"
description="Mathematician and writer, the first computer programmer."
/>
</adw:ToolbarView.content>
</adw:ToolbarView>
</adw:NavigationPage.child>
</adw:NavigationPage>
</adw:NavigationView>
app.ts
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const view = Builder.load({ path: '~/views', name: 'navigation-view' }) as Adw.NavigationView;
const open = view.getViewById<Gtk.Button>('open_button')!;
open.connect('clicked', () => view.push_by_tag('detail'));
app.ts (Blueprint)
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './navigation-view.blp?shared-tree';
const view = build(tree) as Adw.NavigationView;
const open = view.getViewById<Gtk.Button>('open_button')!;
open.connect('clicked', () => view.push_by_tag('detail'));
Solid
TypeScript
// 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>
);
Vue
Vue
<!-- 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>
React
TypeScript
// 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>
);

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.

Why not 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.

Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.Sidebar 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";
}
}
}
TypeScript
TypeScript
import { build } from './sidebar.blp';
const { sidebar } = build();
sidebar.connect('activated', () => console.log(`Open mailbox ${sidebar.selected}`));
Web Components
index.html
HTML
<adw-sidebar id="sidebar" mode="sidebar" selected="0">
<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>
<script type="module">
import '@gjsify/adwaita-web';
const sidebar = document.querySelector('#sidebar');
sidebar.addEventListener('activated', () => console.log(`Open mailbox ${sidebar.selected}`));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './sidebar.blp?shared-tree';
mountSharedTree(tree);
const sidebar = document.querySelector('#sidebar');
sidebar.addEventListener('activated', () => console.log(`Open mailbox ${sidebar.selected}`));
NativeScript
views/sidebar.xml
XML
<adw:Sidebar
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
id="sidebar"
mode="sidebar"
selected="0"
>
<adw:SidebarSection title="Mailboxes">
<adw:SidebarItem
title="Inbox"
subtitle="3 unread"
iconName="mail-unread-symbolic"
/>
<adw:SidebarItem
title="Starred"
subtitle="Favourites"
iconName="starred-symbolic"
/>
<adw:SidebarItem
title="Sent"
subtitle="Outgoing mail"
iconName="mail-send-symbolic"
/>
<adw:SidebarItem
title="Archive"
subtitle="Older mail"
iconName="folder-symbolic"
/>
</adw:SidebarSection>
</adw:Sidebar>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const sidebar = Builder.load({ path: '~/views', name: 'sidebar' }) as Adw.Sidebar;
sidebar.connect('activated', () => console.log(`Open mailbox ${sidebar.selected}`));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './sidebar.blp?shared-tree';
const sidebar = build(tree) as Adw.Sidebar;
sidebar.connect('activated', () => console.log(`Open mailbox ${sidebar.selected}`));

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.

Closing the sheet, and what the preview shows

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 the code below wires to the button; the preview builds the file alone, so there the button shows the content pane and does nothing.

Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.BottomSheet sheet {
open: true;
modal: true;
can-close: true;
content: Gtk.Button toggle {
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";
}
}
}
};
}
TypeScript
TypeScript
import { build } from './bottom-sheet.blp';
const { sheet, toggle } = build();
toggle.connect('clicked', () => {
sheet.open = !sheet.open;
});
Web Components
index.html
HTML
<adw-bottom-sheet id="sheet" open modal can-close>
<gtk-button
id="toggle"
label="Toggle sheet"
halign="center"
valign="center"
class="pill suggested-action"
slot="content"
></gtk-button>
<gtk-box
orientation="vertical"
spacing="12"
margin-start="18"
margin-end="18"
margin-top="18"
margin-bottom="24"
style="margin-inline-start: 18px; margin-inline-end: 18px; margin-top: 18px; margin-bottom: 24px;"
slot="sheet"
>
<gtk-label label="Share track" xalign="0" class="title-2"></gtk-label>
<adw-preferences-group>
<adw-action-row title="Copy link" activatable>
<gtk-image icon-name="edit-copy-symbolic" slot="prefix"></gtk-image>
</adw-action-row>
<adw-action-row title="Send to a friend" activatable>
<gtk-image icon-name="mail-send-symbolic" slot="prefix"></gtk-image>
</adw-action-row>
<adw-action-row title="Add to playlist" activatable>
<gtk-image icon-name="list-add-symbolic" slot="prefix"></gtk-image>
</adw-action-row>
</adw-preferences-group>
</gtk-box>
</adw-bottom-sheet>
<script type="module">
import '@gjsify/adwaita-web';
const sheet = document.querySelector('#sheet');
const toggle = document.querySelector('#toggle');
toggle.addEventListener('click', () => {
sheet.open = !sheet.open;
});
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './bottom-sheet.blp?shared-tree';
mountSharedTree(tree);
const sheet = document.querySelector('#sheet');
const toggle = document.querySelector('#toggle');
toggle.addEventListener('click', () => {
sheet.open = !sheet.open;
});
NativeScript
views/bottom-sheet.xml
XML
<adw:BottomSheet
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
id="sheet"
open="true"
modal="true"
canClose="true"
>
<adw:BottomSheet.content>
<gtk:Button
id="toggle"
label="Toggle sheet"
halign="center"
valign="center"
styleClasses="pill suggested-action"
/>
</adw:BottomSheet.content>
<adw:BottomSheet.sheet>
<gtk:Box
orientation="vertical"
spacing="12"
marginStart="18"
marginEnd="18"
marginTop="18"
marginBottom="24"
>
<gtk:Label label="Share track" xalign="0" styleClasses="title-2" />
<adw:PreferencesGroup>
<adw:ActionRow title="Copy link" activatable="true">
<adw:ActionRow.prefix>
<gtk:Image iconName="edit-copy-symbolic" />
</adw:ActionRow.prefix>
</adw:ActionRow>
<adw:ActionRow title="Send to a friend" activatable="true">
<adw:ActionRow.prefix>
<gtk:Image iconName="mail-send-symbolic" />
</adw:ActionRow.prefix>
</adw:ActionRow>
<adw:ActionRow title="Add to playlist" activatable="true">
<adw:ActionRow.prefix>
<gtk:Image iconName="list-add-symbolic" />
</adw:ActionRow.prefix>
</adw:ActionRow>
</adw:PreferencesGroup>
</gtk:Box>
</adw:BottomSheet.sheet>
</adw:BottomSheet>
app.ts
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const sheet = Builder.load({ path: '~/views', name: 'bottom-sheet' }) as Adw.BottomSheet;
const toggle = sheet.getViewById<Gtk.Button>('toggle')!;
toggle.connect('clicked', () => {
sheet.open = !sheet.open;
});
app.ts (Blueprint)
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './bottom-sheet.blp?shared-tree';
const sheet = build(tree) as Adw.BottomSheet;
const toggle = sheet.getViewById<Gtk.Button>('toggle')!;
toggle.connect('clicked', () => {
sheet.open = !sheet.open;
});
  • View Switching: for switching between peer views instead of moving through a stack.
  • Layout: the Clamp and ToolbarView these navigation surfaces are built on.
  • Native Adwaita Apps: a ready-made NavigationSplitView shell you can drop views into.
  • Adwaita Storybook: the same widgets in a live component browser you can poke at.