Skip to content

Adwaita

Adwaita is GNOME’s design system for GTK 4: the rows, buttons, header bars and dialogs a GNOME app is made of. GJSify gives you the whole set from TypeScript, and the same widget renders as a real Libadwaita widget on the desktop and as a custom element in the browser. It is one design system GJSify supports, not a requirement; the GTK widgets libadwaita does not ship are under Gtk.

Declare the widget tree in a Blueprint .blp file and keep TypeScript for what the widgets do. gjsify build compiles an imported .blp to GtkBuilder XML in process, with no blueprint-compiler installed, and xgettext finds every _("…") string in it. Native Adwaita Apps turns one into a window class. Reach for a UI framework only where the tree changes shape with your data.

On the desktop the widgets come from the Libadwaita library already installed on the machine. @girs/adw-1 is the type package for it, and nothing else is needed:

TypeScript
import Adw from 'gi://Adw?version=1';
const row = new Adw.SwitchRow({ title: 'Automatic updates', active: true });

For the browser, install the port and import it once. That registers every element and applies the stylesheet, so from there you write HTML:

Terminal
gjsify install @gjsify/adwaita-web
TypeScript
import '@gjsify/adwaita-web';
HTML
<adw-preferences-group title="Updates">
<adw-switch-row title="Automatic updates" active></adw-switch-row>
</adw-preferences-group>

The package entry is TypeScript, so it goes through your bundler like any other dependency: gjsify build --app browser, the gjsifyBrowser() Vite preset, or whatever you already use. To change how any of it looks, set the design tokens.

PackageWhat it holds
@gjsify/adwaita-webthe custom elements and the compiled stylesheet
@gjsify/adwaita-corethe behaviour both ports share, with no rendering
@gjsify/adwaita-fontsAdwaita Sans as TTF files plus the @font-face CSS
@gjsify/adwaita-iconsthe symbolic icons as importable SVG strings

Every widget block is two windows. The widget runs in the first, in the browser port, so it follows the light and dark toggle. The source window below it has Blueprint, which declares the widget, and TypeScript, which builds it. Choose a segment once and every block on the site follows it.

More

The More segment in the source window opens a menu of the same widget for the Web Components and NativeScript ports and for Solid, Vue and React through @gjsify/gtk-host. A port whose program is several files lists them under its name, one row each. A widget built in code rather than declared simply has no entry.

Libadwaita and GTK are the reference, typed by @girs/adw-1 and @girs/gtk-4.0. Both ports follow their naming and export the widgets as Adw and Gtk namespaces, beside a Gio namespace that holds Gio.Menu and Gio.MenuItem, so import { Adw, Gio } from '@gjsify/adwaita-web' builds a menu model the way GJS does. Built with --gi-renderer, a browser or NativeScript bundle also answers gi://Adw and gi://Gtk from the port, and refuses gi://Gio by name.

On the desktop

Real Libadwaita widgets, typed by @girs/adw-1: Adw.SwitchRow, Adw.HeaderBar, Adw.NavigationSplitView. This is the reference the other two ports follow, and the code runs unchanged on GJS, Node.js, Bun and Deno.

In the browser

@gjsify/adwaita-web gives you the same widgets as custom elements: <adw-switch-row>, <adw-header-bar>.

On Android and iOS

@gjsify/adwaita-nativescript reproduces the Adwaita look with real NativeScript views rather than a webview: Adw.SwitchRow, Adw.HeaderBar. Some widgets are simplified, and each page says where.

The web and NativeScript ports share one behaviour layer, so a fix to a toast queue or a spin-button clamp lands in both at once. For the mechanics, see How It Works.

The Adwaita Storybook is a component browser: pick a widget, change its properties from a controls panel, and watch it update. Run it as a native window with gjsify storybook.

Ready to assemble a whole app out of these? Start with Native Adwaita Apps.