Skip to content

Feedback

Two ways to tell the user something. A toast slides a short message over the content and gets out of the way on its own, so use it when nothing needs to happen next. A dialog puts a modal surface in front of the window and waits for an answer, so use it when something does. This page also covers the two dialogs every app is expected to have: About and Preferences.

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.

A short line of text, optionally with one action button, that floats over the content for a few seconds and then dismisses itself. You do not add a toast to your layout: you wrap the page in a toast overlay and hand it toasts at runtime. The overlay layers them above the bottom edge without displacing anything beneath.

The overlay shows one toast at a time. Add a second while the first is visible and it waits its turn, appearing once the visible one goes away, whether by its timeout, its close button, its action button or an explicit dismissal. Every port behaves the same way here, so a queue you tested in the browser holds on the desktop.

Adw.Toast
HTML
<adw-toast-overlay id="overlay" style="width: 360px; height: 220px;">
<adw-status-page title="Documents" description="Drag files here to organise them."></adw-status-page>
</adw-toast-overlay>
<script type="module">
// Toasts are shown imperatively: the overlay floats them over its content.
// The timeout is in seconds; a second toast added now would be queued and
// shown once this one is dismissed.
const overlay = document.getElementById('overlay');
overlay.addToast('File moved to Trash', { timeout: 3, buttonLabel: 'Undo' });
overlay.pendingToasts; // how many are waiting behind the visible one
overlay.dismiss(); // dismiss now and show the next queued toast
</script>
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
// The overlay wraps the page content and floats toasts above it.
const overlay = new Adw.ToastOverlay({ child: content });
const toast = new Adw.Toast({
title: 'File moved to Trash',
timeout: 3,
buttonLabel: 'Undo',
});
overlay.add_toast(toast);
Code
using Gtk 4.0;
using Adw 1;
// A toast is CONSTRUCTED, not declared. `Adw.ToastOverlay` is the part a
// blueprint carries, and `overlay.add_toast(...)` presents one at runtime.
Adw.ToastOverlay overlay {
child: Adw.StatusPage {
title: _("Documents");
description: _("Drag files here to organise them.");
};
}
UI frameworks

No Solid, Vue or React snippet for Adw.Toast: AdwToast is a GObject, not a GtkWidget: it has no tag in a table of concrete widgets.

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.

NativeScript
TypeScript
import { AdwToastOverlay } from '@gjsify/adwaita-nativescript';
const overlay = new AdwToastOverlay();
overlay.setContent(content);
// The NativeScript overlay timer is in milliseconds.
overlay.showToast('File moved to Trash', {
timeout: 3000,
buttonLabel: 'Undo',
});

No NativeScript XML template for Adw.Toast: AdwToastOverlay takes no XML child (it overrides no _addChildFromBuilder) and a toast is raised by calling showToast(), which is not markup.

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.

Ask a question the user has to answer: a bold heading, some body text and a row of responses to choose between. Each response is a button with a string ID, and that ID is what reaches your code. Give one response a destructive or suggested appearance to mark the consequence, and set the default and close responses so Enter and Escape do the right thing.

Adw.AlertDialog
HTML
<adw-alert-dialog
open
heading="Delete Project?"
body="This will permanently remove the project and all of its files. This action cannot be undone."
>
<adw-alert-response id="cancel">Cancel</adw-alert-response>
<adw-alert-response id="delete" appearance="destructive">Delete</adw-alert-response>
</adw-alert-dialog>
<adw-alert-dialog> observes these in the browser port. Changing one re-renders the element. The second column is what the preview one tab to the left writes, read across every copy of the element in it. The other windows name their own properties.
Attribute In this preview
heading Delete Project?
body This will permanently remove the project and all of its files. This action cannot be undone.
heading-use-markup not used
body-use-markup not used
close-response not used
default-response not used
open set
prefer-wide-layout not used
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
const dialog = new Adw.AlertDialog({
heading: 'Delete Project?',
body: 'This will permanently remove the project and all of its files. This action cannot be undone.',
});
dialog.add_response('cancel', 'Cancel');
dialog.add_response('delete', 'Delete');
dialog.set_response_appearance('delete', Adw.ResponseAppearance.DESTRUCTIVE);
dialog.set_default_response('cancel');
dialog.set_close_response('cancel');
dialog.present(parent);
Code
using Gtk 4.0;
using Adw 1;
Adw.AlertDialog {
heading: _("Delete Project?");
body: _("This will permanently remove the project and all of its files. This action cannot be undone.");
responses [
cancel: _("Cancel"),
delete: _("Delete") destructive,
]
default-response: "cancel";
close-response: "cancel";
}
UI frameworks

No Solid, Vue or React snippet for Adw.AlertDialog: its responses are add_response() calls and it is shown with present(); neither is markup.

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.

NativeScript
TypeScript
import { AdwAlertDialog } from '@gjsify/adwaita-nativescript';
const dialog = new AdwAlertDialog(
'Delete Project?',
'This will permanently remove the project and all of its files. This action cannot be undone.',
);
dialog.addResponse('cancel', 'Cancel');
dialog.addResponse('delete', 'Delete');
dialog.defaultResponse = 'cancel';
dialog.closeResponse = 'cancel';
// NS substitutes the platform's native confirm() chrome; present() resolves
// to the chosen response ID.
const response = await dialog.present();

No NativeScript XML template for Adw.AlertDialog: AdwAlertDialog extends Observable, not View: it has no place in a view tree.

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.

Wire this to the About entry in your primary menu and you are done. Fill in the app metadata and the dialog builds the rest: a large icon, the application name, a developer line, a version pill, and Details, Credits and Legal sub-pages you never lay out yourself.

Adw.AboutDialog
HTML
<!-- developers / designers are string arrays set as properties, e.g.
el.developers = ['Ada Lovelace', 'Grace Hopper']; -->
<adw-about-dialog
open
application-name="Adwaita Storybook"
developer-name="A GJSify Project"
version="0.11.0"
comments="A live catalogue of native GTK and Adwaita widgets, rendered under GJS."
website="https://github.com/gjsify/gjsify"
issue-url="https://github.com/gjsify/gjsify/issues"
license="The MIT License (MIT)"
copyright="© 2026 The GJSify Project"
></adw-about-dialog>
<adw-about-dialog> observes these in the browser port. Changing one re-renders the element. The second column is what the preview one tab to the left writes, read across every copy of the element in it. The other windows name their own properties.
Attribute In this preview
application-name Adwaita Storybook
application-icon not used
developer-name A GJSify Project
version 0.11.0
comments A live catalogue of native GTK and Adwaita widgets, rendered under GJS.
website https://github.com/gjsify/gjsify
support-url not used
issue-url https://github.com/gjsify/gjsify/issues
copyright © 2026 The GJSify Project
license The MIT License (MIT)
license-url not used
open set
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
const dialog = new Adw.AboutDialog({
applicationName: 'Adwaita Storybook',
applicationIcon: 'application-x-executable-symbolic',
developerName: 'A GJSify Project',
version: '0.11.0',
comments: 'A live catalogue of native GTK and Adwaita widgets, rendered under GJS.',
website: 'https://github.com/gjsify/gjsify',
issueUrl: 'https://github.com/gjsify/gjsify/issues',
licenseType: Gtk.License.MIT_X11,
developers: ['Ada Lovelace', 'Grace Hopper'],
designers: ['Margaret Hamilton'],
copyright: '© 2026 The GJSify Project',
});
dialog.present(parent);
Code
using Gtk 4.0;
using Adw 1;
Adw.AboutDialog {
application-name: _("Adwaita Storybook");
developer-name: _("A GJSify Project");
version: "0.11.0";
comments: _("A live catalogue of native GTK and Adwaita widgets, rendered under GJS.");
website: "https://github.com/gjsify/gjsify";
issue-url: "https://github.com/gjsify/gjsify/issues";
license-type: mit_x11;
copyright: "© 2026 The GJSify Project";
}
UI frameworks

No Solid, Vue or React snippet for Adw.AboutDialog: a dialog is opened with present(), so a static tree renders nothing a reader would see.

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.

NativeScript
TypeScript
import { AdwAboutDialog } from '@gjsify/adwaita-nativescript';
const dialog = new AdwAboutDialog();
dialog.applicationName = 'Adwaita Storybook';
dialog.applicationIcon = '🗔';
dialog.version = '0.11.0';
// NS exposes only scalar fields: credits fold into the developer line.
dialog.developerName = 'A GJSify Project · Ada Lovelace, Grace Hopper';
dialog.comments = 'A live catalogue of native GTK and Adwaita widgets, rendered under GJS.';
dialog.website = 'https://github.com/gjsify/gjsify';
dialog.copyright = '© 2026 The GJSify Project';
dialog.present();
XML
<adw:AdwAboutDialog
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
applicationName="Adwaita Gallery"
version="1.0.0"
developerName="The GNOME Project"
comments="A tour of the Adwaita widgets on NativeScript."
website="https://gjsify.org"
copyright="© 2026 The GNOME Project"
open="false"
/>

The container behind your Preferences command. Organise it into pages, each holding groups of boxed-list rows: switches, drop-downs, spin buttons. Most apps need only one page, which is what the example shows. In a native window, extra pages sit behind a view switcher and you see one at a time; the web port renders them stacked instead.

Adw.PreferencesDialog
HTML
<adw-preferences-dialog open title="Preferences">
<adw-preferences-page title="General" icon-name="preferences-system-symbolic">
<adw-preferences-group title="Appearance" description="Control how the application looks and behaves.">
<adw-switch-row title="Dark style" subtitle="Use a dark colour scheme" active></adw-switch-row>
<adw-combo-row title="Accent colour" items='["Blue","Teal","Green","Orange","Purple"]' selected="0"></adw-combo-row>
<adw-spin-row title="Font size" min="8" max="24" value="12" step="1"></adw-spin-row>
</adw-preferences-group>
</adw-preferences-page>
</adw-preferences-dialog>
<adw-preferences-dialog> observes these in the browser port. Changing one re-renders the element. The second column is what the preview one tab to the left writes, read across every copy of the element in it. The other windows name their own properties.
Attribute In this preview
title Preferences
open set
can-close not used
content-width not used
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
const dialog = new Adw.PreferencesDialog();
const page = new Adw.PreferencesPage({
title: 'General',
iconName: 'preferences-system-symbolic',
});
const group = new Adw.PreferencesGroup({
title: 'Appearance',
description: 'Control how the application looks and behaves.',
});
group.add(new Adw.SwitchRow({ title: 'Dark style', subtitle: 'Use a dark colour scheme', active: true }));
group.add(new Adw.ComboRow({
title: 'Accent colour',
model: new Gtk.StringList({ strings: ['Blue', 'Teal', 'Green', 'Orange', 'Purple'] }),
selected: 0,
}));
group.add(new Adw.SpinRow({
title: 'Font size',
adjustment: new Gtk.Adjustment({ lower: 8, upper: 24, value: 12, stepIncrement: 1 }),
}));
page.add(group);
dialog.add(page);
dialog.present(parent);
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesDialog {
title: _("Preferences");
Adw.PreferencesPage {
title: _("General");
icon-name: "preferences-system-symbolic";
Adw.PreferencesGroup {
title: _("Appearance");
description: _("Control how the application looks and behaves.");
Adw.SwitchRow {
title: _("Dark style");
subtitle: _("Use a dark colour scheme");
active: true;
}
Adw.ComboRow {
title: _("Accent colour");
selected: 0;
model: Gtk.StringList {
strings [_("Blue"), _("Teal"), _("Green"), _("Orange"), _("Purple")]
};
}
Adw.SpinRow {
title: _("Font size");
adjustment: Gtk.Adjustment {
lower: 8;
upper: 24;
value: 12;
step-increment: 1;
};
}
}
}
}
UI frameworks

No Solid, Vue or React snippet for Adw.PreferencesDialog: uncurated-placement: a page cannot be a child of AdwPreferencesDialog.

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.

NativeScript
TypeScript
import {
AdwPreferencesDialog,
AdwPreferencesPage,
AdwPreferencesGroup,
AdwSwitchRow,
AdwComboRow,
AdwSpinRow,
} from '@gjsify/adwaita-nativescript';
const dialog = new AdwPreferencesDialog();
dialog.title = 'Preferences';
const page = new AdwPreferencesPage();
const group = new AdwPreferencesGroup();
group.title = 'Appearance';
const darkStyle = new AdwSwitchRow();
darkStyle.title = 'Dark style';
darkStyle.subtitle = 'Use a dark colour scheme';
darkStyle.active = true;
group.addRow(darkStyle);
const accent = new AdwComboRow();
accent.title = 'Accent colour';
accent.options = ['Blue', 'Teal', 'Green', 'Orange', 'Purple'].map((label) => ({ label, value: label }));
accent.selected = 0;
group.addRow(accent);
const fontSize = new AdwSpinRow();
fontSize.title = 'Font size';
fontSize.min = 8;
fontSize.max = 24;
fontSize.value = 12;
group.addRow(fontSize);
page.addGroup(group);
dialog.add(page);
dialog.present();
XML
<adw:AdwPreferencesDialog
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
title="Preferences"
open="false"
>
<adw:AdwPreferencesPage title="General">
<adw:AdwPreferencesGroup title="Appearance">
<adw:AdwSwitchRow
title="Dark mode"
active="true"
/>
</adw:AdwPreferencesGroup>
</adw:AdwPreferencesPage>
</adw:AdwPreferencesDialog>
  • Boxed Lists: the rows a preferences dialog is built from.
  • Presentation: banners and status pages, for messages that do not interrupt.
  • Layout: the header bars and toolbar views these overlays sit above.
  • Adwaita Storybook: the same widgets in a live component browser you can poke at.