Skip to content

Presentation

These widgets show state and identity rather than collect input. Use an avatar for a person or account, a banner for an in-context message, a shortcut label to draw a keyboard shortcut as keycaps, a spinner while work is running, a status page for an empty or error state, and a window title in a header bar.

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.

Stand in for a person, account or entity. Ask for initials and give it a name, and it derives the initials and a stable background colour from that name; with either missing it falls back to a symbolic icon. Size scales it from an inline list glyph up to a full profile header.

Adw.Avatar
HTML
<adw-avatar text="Ada Lovelace" size="96" show-initials icon="avatar-default"></adw-avatar>
<adw-avatar> 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
text Ada Lovelace
size 96
show-initials set
icon avatar-default
custom-image not used
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
const avatar = new Adw.Avatar({
size: 96,
text: 'Ada Lovelace',
showInitials: true,
iconName: 'avatar-default-symbolic',
});
Code
using Gtk 4.0;
using Adw 1;
Adw.Avatar {
text: "Ada Lovelace";
size: 96;
show-initials: true;
icon-name: "avatar-default-symbolic";
}
UI frameworks
TypeScript
// mount(() => <AdwAvatar />, container) — from '@gjsify/gtk-host/solid'
const AdwAvatar = () => (
<adw-avatar
text="Ada Lovelace"
size={96}
showInitials
iconName="avatar-default-symbolic"
/>
);
Vue
<!-- AdwAvatar.vue — mount(AdwAvatar, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-avatar
text="Ada Lovelace"
:size="96"
:show-initials="true"
icon-name="avatar-default-symbolic"
/>
</template>
TypeScript
// createRoot(container).render(<AdwAvatar />) — from '@gjsify/gtk-host/react'
const AdwAvatar = () => (
<adw-avatar
text="Ada Lovelace"
size={96}
showInitials
iconName="avatar-default-symbolic"
/>
);
NativeScript
TypeScript
import { AdwAvatar } from '@gjsify/adwaita-nativescript';
const avatar = new AdwAvatar();
avatar.text = 'Ada Lovelace';
avatar.size = 96;
// The NS avatar always shows derived initials. The CSS-subset widget has no
// icon-theme lookup, so `showInitials` / `iconName` have no equivalent.
XML
<adw:AdwAvatar
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
text="Ada Lovelace"
size="96"
/>

Surface one in-context status message across the top of a view: a metered connection, an offline state, a pending action. A banner takes a title and an optional action button, and it reveals and hides itself, so it only occupies space while the message applies.

Adw.Banner
HTML
<adw-banner title="Metered connection: updates paused" button-label="Resume" revealed></adw-banner>
<adw-banner> 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 Metered connection: updates paused
button-label Resume
revealed set
use-markup not used
button-style not used
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
const banner = new Adw.Banner({
title: 'Metered connection: updates paused',
buttonLabel: 'Resume',
revealed: true,
});
// The banner is full-width; a sized container gives it a width to fill.
const container = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, widthRequest: 460 });
container.append(banner);
Code
using Gtk 4.0;
using Adw 1;
Adw.Banner {
title: _("Metered connection: updates paused");
button-label: _("Resume");
revealed: true;
}
UI frameworks
TypeScript
// mount(() => <AdwBanner />, container) — from '@gjsify/gtk-host/solid'
const AdwBanner = () => (
<adw-banner
title="Metered connection: updates paused"
buttonLabel="Resume"
revealed
/>
);
Vue
<!-- AdwBanner.vue — mount(AdwBanner, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-banner
title="Metered connection: updates paused"
button-label="Resume"
:revealed="true"
/>
</template>
TypeScript
// createRoot(container).render(<AdwBanner />) — from '@gjsify/gtk-host/react'
const AdwBanner = () => (
<adw-banner
title="Metered connection: updates paused"
buttonLabel="Resume"
revealed
/>
);
NativeScript
TypeScript
import { AdwBanner } from '@gjsify/adwaita-nativescript';
const banner = new AdwBanner();
banner.title = 'Metered connection: updates paused';
banner.buttonLabel = 'Resume';
banner.revealed = true;
// No `useMarkup`: the NS banner label is plain text (no rich-text subset).
XML
<adw:AdwBanner
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
title="Metered connection: updates paused"
buttonLabel="Resume"
revealed="true"
/>

Draw a keyboard shortcut as keycaps instead of writing it out in prose. You give it one accelerator string and it renders the keys in GTK’s fixed order, whatever order you wrote the modifiers in. The string has four levels: a space lists alternatives, ... is a range, + means the shortcuts are pressed in sequence, and & means they are held together. An empty accelerator shows the disabled-text placeholder instead.

Adw.ShortcutLabel
HTML
<div style="display:flex;flex-wrap:wrap;gap:24px;justify-content:center;align-items:center;">
<adw-shortcut-label accelerator="&lt;Control&gt;C"></adw-shortcut-label>
<adw-shortcut-label accelerator="&lt;Shift&gt;A Home"></adw-shortcut-label>
<adw-shortcut-label accelerator="&lt;Alt&gt;1...9"></adw-shortcut-label>
<adw-shortcut-label accelerator="Control_L&amp;Control_R"></adw-shortcut-label>
<adw-shortcut-label accelerator="" disabled-text="Disabled"></adw-shortcut-label>
</div>
<adw-shortcut-label> 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
accelerator <Control>C
disabled-text Disabled
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
// Ctrl+C. The keycap order is GTK's, not the string's: `<Shift><Control>a`
// renders as Ctrl Shift A.
const shortcut = new Adw.ShortcutLabel({ accelerator: '<Control>C' });
// A space lists alternatives, `...` a range, `+` a sequence, `&` keys held together.
const alternatives = new Adw.ShortcutLabel({ accelerator: '<Shift>A Home' });
const range = new Adw.ShortcutLabel({ accelerator: '<Alt>1...9' });
const together = new Adw.ShortcutLabel({ accelerator: 'Control_L&Control_R' });
// With no accelerator the placeholder is shown instead.
const unset = new Adw.ShortcutLabel({ accelerator: '', disabledText: 'Disabled' });
Code
using Gtk 4.0;
using Adw 1;
Gtk.Box {
orientation: horizontal;
spacing: 24;
Adw.ShortcutLabel {
accelerator: "<Control>C";
}
Adw.ShortcutLabel {
accelerator: "<Shift>A Home";
}
Adw.ShortcutLabel {
accelerator: "<Alt>1...9";
}
Adw.ShortcutLabel {
accelerator: "Control_L&Control_R";
}
Adw.ShortcutLabel {
accelerator: "";
disabled-text: _("Disabled");
}
}
UI frameworks
TypeScript
// mount(() => <AdwShortcutLabel />, container) — from '@gjsify/gtk-host/solid'
const AdwShortcutLabel = () => (
<adw-shortcut-label accelerator="<Control>C" />
);
Vue
<!-- AdwShortcutLabel.vue — mount(AdwShortcutLabel, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-shortcut-label accelerator="<Control>C" />
</template>
TypeScript
// createRoot(container).render(<AdwShortcutLabel />) — from '@gjsify/gtk-host/react'
const AdwShortcutLabel = () => (
<adw-shortcut-label accelerator="<Control>C" />
);
NativeScript
TypeScript
import { AdwShortcutLabel } from '@gjsify/adwaita-nativescript';
const shortcut = new AdwShortcutLabel();
// `disabledText` first: an empty accelerator renders the placeholder, so the
// placeholder has to be in place before the accelerator triggers the rebuild.
shortcut.disabledText = 'Disabled';
shortcut.accelerator = '<Control>C';
XML
<adw:AdwShortcutLabel
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
accelerator="&lt;Control>C"
/>

Show that work is running when you cannot say how much is left. The spinner turns continuously while visible and scales to any size, from an inline glyph to a page-filling placeholder. If you do know the progress, use a progress bar instead.

Adw.Spinner
HTML
<adw-spinner size="48"></adw-spinner>
<adw-spinner> 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
size 48
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
const spinner = new Adw.Spinner({
widthRequest: 48,
heightRequest: 48,
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
});
Code
using Gtk 4.0;
using Adw 1;
Adw.Spinner {
width-request: 48;
height-request: 48;
}
UI frameworks
TypeScript
// mount(() => <AdwSpinner />, container) — from '@gjsify/gtk-host/solid'
const AdwSpinner = () => (
<adw-spinner
widthRequest={48}
heightRequest={48}
/>
);
Vue
<!-- AdwSpinner.vue — mount(AdwSpinner, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-spinner
:width-request="48"
:height-request="48"
/>
</template>
TypeScript
// createRoot(container).render(<AdwSpinner />) — from '@gjsify/gtk-host/react'
const AdwSpinner = () => (
<adw-spinner
widthRequest={48}
heightRequest={48}
/>
);
NativeScript
TypeScript
import { AdwSpinner } from '@gjsify/adwaita-nativescript';
const spinner = new AdwSpinner();
spinner.size = 48;
XML
<adw:AdwSpinner
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
spinning="true"
size="48"
/>

Fill an empty, error or welcome screen with a centred placeholder: a large symbolic icon, a title and a description. Add a child widget to give the user a way out, such as a button that creates their first document.

Adw.StatusPage
HTML
<adw-status-page icon="folder" title="No Documents" description="Documents you create or open will appear here.">
<gtk-button label="New Document" pill suggested></gtk-button>
</adw-status-page>
<adw-status-page> 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
icon folder
title No Documents
description Documents you create or open will appear here.
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
const button = new Gtk.Button({
label: 'New Document',
halign: Gtk.Align.CENTER,
cssClasses: ['pill', 'suggested-action'],
});
const statusPage = new Adw.StatusPage({
iconName: 'folder-symbolic',
title: 'No Documents',
description: 'Documents you create or open will appear here.',
child: button,
});
Code
using Gtk 4.0;
using Adw 1;
Adw.StatusPage {
icon-name: "folder-symbolic";
title: _("No Documents");
description: _("Documents you create or open will appear here.");
child: Gtk.Button {
label: _("New Document");
halign: center;
styles ["pill", "suggested-action"]
};
}
UI frameworks
TypeScript
// mount(() => <AdwStatusPage />, container) — from '@gjsify/gtk-host/solid'
const AdwStatusPage = () => (
<adw-status-page
iconName="folder-symbolic"
title="No Documents"
description="Documents you create or open will appear here."
>
<gtk-button
label="New Document"
cssClasses={['pill', 'suggested-action']}
halign="center"
/>
</adw-status-page>
);
Vue
<!-- AdwStatusPage.vue — mount(AdwStatusPage, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-status-page
icon-name="folder-symbolic"
title="No Documents"
description="Documents you create or open will appear here."
>
<gtk-button
label="New Document"
:css-classes="['pill', 'suggested-action']"
halign="center"
/>
</adw-status-page>
</template>
TypeScript
// createRoot(container).render(<AdwStatusPage />) — from '@gjsify/gtk-host/react'
const AdwStatusPage = () => (
<adw-status-page
iconName="folder-symbolic"
title="No Documents"
description="Documents you create or open will appear here."
>
<gtk-button
label="New Document"
cssClasses={['pill', 'suggested-action']}
halign="center"
/>
</adw-status-page>
);
NativeScript
TypeScript
import { GtkButton, AdwStatusPage } from '@gjsify/adwaita-nativescript';
import { folderSymbolic } from '@gjsify/adwaita-icons/places';
const button = new GtkButton();
button.text = 'New Document';
button.variant = 'suggested-action';
const page = new AdwStatusPage();
page.iconName = folderSymbolic; // an Adwaita symbolic icon (SVG string)
page.title = 'No Documents';
page.description = 'Documents you create or open will appear here.';
page.setChild(button);
XML
<adw:AdwStatusPage
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
iconText="📁"
title="No Documents"
description="Documents you create or open will appear here."
/>

The title widget a header bar expects: a title with an optional subtitle, already sized and styled for that spot. Leave the subtitle empty and it collapses, so a single-line title stays vertically centred.

Adw.WindowTitle
HTML
<adw-window-title title="Inbox" subtitle="3 unread messages"></adw-window-title>
<adw-window-title> 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 Inbox
subtitle 3 unread messages
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
const windowTitle = new Adw.WindowTitle({
title: 'Inbox',
subtitle: '3 unread messages',
});
Code
using Gtk 4.0;
using Adw 1;
Adw.WindowTitle {
title: _("Inbox");
subtitle: _("3 unread messages");
}
UI frameworks
TypeScript
// mount(() => <AdwWindowTitle />, container) — from '@gjsify/gtk-host/solid'
const AdwWindowTitle = () => (
<adw-window-title
title="Inbox"
subtitle="3 unread messages"
/>
);
Vue
<!-- AdwWindowTitle.vue — mount(AdwWindowTitle, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-window-title
title="Inbox"
subtitle="3 unread messages"
/>
</template>
TypeScript
// createRoot(container).render(<AdwWindowTitle />) — from '@gjsify/gtk-host/react'
const AdwWindowTitle = () => (
<adw-window-title
title="Inbox"
subtitle="3 unread messages"
/>
);
NativeScript
TypeScript
import { AdwWindowTitle } from '@gjsify/adwaita-nativescript';
const windowTitle = new AdwWindowTitle();
windowTitle.title = 'Inbox';
windowTitle.subtitle = '3 unread messages';
XML
<adw:AdwWindowTitle
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
title="Inbox"
subtitle="3 unread messages"
/>
  • Boxed Lists: the row widgets for settings and detail screens.
  • Feedback: toasts and dialogs, for messages that interrupt.
  • Layout: the header bar a WindowTitle goes into.
  • Adwaita Storybook: the same widgets in a live component browser you can poke at.