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. The generic dialog takes whatever content you hand it; the alert, about and preferences dialogs below are the three shapes an app asks for often enough that libadwaita writes them for you, plus the shortcuts dialog that lists your keyboard shortcuts.

How the previews differ from the widget

The previews hold each dialog open and each toast visible, which is not how they behave in an app: there they are modal windows and transient overlays the user triggers. Each one also sits in a fixed-height stage here so it stays contained, while a real window sizes and centres itself.

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.

One toast at a time

The overlay layers toasts above the bottom edge without displacing anything beneath, and 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.

Blueprint
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.");
};
}
TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
const content = new Adw.StatusPage({
title: 'Documents',
description: 'Drag files here to organise them.',
});
// 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);
Web Components
HTML
<adw-toast-overlay id="overlay" style="width: 360px; height: 220px;">
<!-- description: The description markup to be displayed below the title. -->
<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>

The dialog the other three are built on, and the one to reach for when none of them fits: it holds whatever you give it — one child, no content model of its own — and it decides for itself whether to be a floating card over the window or a bottom sheet across it. Give it a title and a flat header with a close button appears; leave the title off and the content runs to the edges.

How a dialog is dismissed

Three ways out, and one lock. Escape, the scrim and the close button all close the dialog, and all three are gated by can-close: while it is off, each of them raises a close-attempt instead and the dialog stays put — which is how you keep a user in a half-filled form. force_close() ignores the lock, so a program can always leave. Every close returns focus to the element that was focused before present().

Sizing it

Content width and content height bound the card, and the browser port defaults the width to the same 360 px Adw.Dialog asks for. On a viewport narrower than that the dialog switches to a bottom sheet unless you pin presentation mode to floating.

Blueprint
Code
using Gtk 4.0;
using Adw 1;
// A dialog is PRESENTED, not shown: a `.blp` declares it, and the code that owns
// the window calls `present()` on the object it built.
Adw.Dialog {
title: _("Preferences");
child: Adw.PreferencesPage {
title: _("General");
Adw.PreferencesGroup {
title: _("Appearance");
Adw.SwitchRow {
title: _("Dark style");
subtitle: _("Use a dark colour scheme");
active: true;
}
}
};
}
TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
Adw.init();
const group = new Adw.PreferencesGroup({ title: 'Appearance' });
group.add(new Adw.SwitchRow({ title: 'Dark style', subtitle: 'Use a dark colour scheme', active: true }));
const page = new Adw.PreferencesPage({ title: 'General' });
page.add(group);
// ONE child, and it is a PROPERTY (`Adw.Dialog:child`), not a pack. The alert,
// about and preferences dialogs are this class with a content model added.
const dialog = new Adw.Dialog({ title: 'Preferences', child: page });
// `close()` honours can-close, `force_close()` does not; both raise `closed`.
dialog.present(window);
Web Components
HTML
<adw-dialog open title="Preferences">
<adw-preferences-page title="General">
<adw-preferences-group title="Appearance">
<!-- active: Whether the switch row is in the "on" or "off" position. -->
<adw-switch-row title="Dark style" subtitle="Use a dark colour scheme" active></adw-switch-row>
</adw-preferences-group>
</adw-preferences-page>
</adw-dialog>

Ask a question the user has to answer: a bold heading, some body text and a row of responses. Each response is a button with a string ID, and that ID is what reaches your code. Set the default and close responses so Enter and Escape do the right thing.

Marking the consequence

Give one response a destructive or suggested appearance and the choice reads at a glance before the reader commits to it.

Blueprint
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.");
// `delete` is the destructive one. A response FLAG (`destructive`, `suggested`,
// `disabled`) is outside the subset `@gjsify/blueprint` holds, so gjsify refuses
// this file rather than build it — set the appearance from code instead, the way
// the GJS tab does with `set_response_appearance`.
responses [
cancel: _("Cancel"),
delete: _("Delete"),
]
default-response: "cancel";
close-response: "cancel";
}
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);
Web Components
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>

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.

Blueprint
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";
}
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);
Web Components
HTML
<!-- developers / designers are string arrays set as properties, e.g.
el.developers = ['Ada Lovelace', 'Grace Hopper']; -->
<!-- issue-url: The URL for the application's issue tracker. -->
<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>
NativeScript
XML
<adw:AboutDialog
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
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. Most apps need only one page, which is what the example shows.

Pages on the two ports

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.

Blueprint
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;
};
}
}
}
}
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);
Web Components
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.">
<!-- active: Whether the switch row is in the "on" or "off" position. -->
<adw-switch-row title="Dark style" subtitle="Use a dark colour scheme" active></adw-switch-row>
<!-- model: The model that provides the displayed items. -->
<adw-combo-row title="Accent colour" model='["Blue","Teal","Green","Orange","Purple"]' selected="0"></adw-combo-row>
<!-- adjustment: The adjustment that holds the value of the spin row. -->
<adw-spin-row title="Font size" value="12" adjustment='{"lower":8,"upper":24}'></adw-spin-row>
</adw-preferences-group>
</adw-preferences-page>
</adw-preferences-dialog>
NativeScript
XML
<adw:PreferencesDialog
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
title="Preferences"
open="false"
>
<adw:PreferencesPage title="General">
<adw:PreferencesGroup title="Appearance">
<adw:SwitchRow
title="Dark mode"
active="true"
/>
</adw:PreferencesGroup>
</adw:PreferencesPage>
</adw:PreferencesDialog>

The window behind your Keyboard Shortcuts command: sections of shortcut rows, each row a title, an optional subtitle and an accelerator drawn as keycaps. New in libadwaita 1.8.

An accelerator, or an action

An item can carry the accelerator itself, or name the action it stands for and let the application supply the key. On GTK that lookup is Gtk.Application.set_accels_for_action, and the accelerator wins whenever the action has none — which is what the class documents. A browser has no application to ask, so <adw-shortcut-label> draws the accelerator that is there and a row that names only an action simply shows no keycaps.

One direction at a time

A row may name the direction it belongs to, so one dialog can list the LTR and the RTL accelerator for the same action and each shows in its own. An untitled section is the documented way to subdivide a section into groups, and libadwaita draws no heading for one.

Blueprint
Code
using Gtk 4.0;
using Adw 1;
// A section and an item are GOBJECTS, so a `.blp` names them as objects and the
// dialog takes each as a child — the GtkBuildable route the class documents.
Adw.ShortcutsDialog shortcuts_dialog {
title: _("Keyboard Shortcuts");
Adw.ShortcutsSection {
title: _("General");
Adw.ShortcutsItem {
title: _("Open Menu");
accelerator: "F10";
}
Adw.ShortcutsItem {
title: _("Preferences");
action-name: "app.preferences";
}
}
}
TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
Adw.init();
// SECTIONS ARE GObjects, not widgets, so they are added rather than packed. An
// untitled section is the documented way to subdivide one into groups.
const general = new Adw.ShortcutsSection({ title: 'General' });
general.add(new Adw.ShortcutsItem({ title: 'Open Menu', accelerator: 'F10' }));
// Or name the action and let the application supply the key.
general.add(new Adw.ShortcutsItem({ title: 'Preferences', action_name: 'app.preferences' }));
general.add(new Adw.ShortcutsItem({ title: 'Quit', accelerator: '<Control>q' }));
const navigation = new Adw.ShortcutsSection();
navigation.add(
new Adw.ShortcutsItem({
title: 'Move Tab Left',
accelerator: '<Shift><Control>Page_Up',
direction: Gtk.TextDirection.LTR,
}),
);
const dialog = new Adw.ShortcutsDialog({ title: 'Keyboard Shortcuts' });
dialog.add(general);
dialog.add(navigation);
dialog.present(window);
Web Components
HTML
<adw-shortcuts-dialog open title="Keyboard Shortcuts">
<!-- title: The title of the section, can be `NULL`. -->
<adw-shortcuts-section title="General">
<adw-shortcuts-item title="Open Menu" accelerator="F10"></adw-shortcuts-item>
<!-- action-name: Fully qualified action name to get the accelerator from. -->
<adw-shortcuts-item title="Preferences" action-name="app.preferences"></adw-shortcuts-item>
<adw-shortcuts-item title="Quit" accelerator="&lt;Control&gt;q"></adw-shortcuts-item>
</adw-shortcuts-section>
<adw-shortcuts-section>
<adw-shortcuts-item title="Move Tab Left" accelerator="&lt;Shift&gt;&lt;Control&gt;Page_Up" direction="ltr"></adw-shortcuts-item>
<adw-shortcuts-item title="Move Tab Left" accelerator="&lt;Shift&gt;&lt;Control&gt;Page_Up" direction="rtl"></adw-shortcuts-item>
</adw-shortcuts-section>
</adw-shortcuts-dialog>
  • 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.