Skip to content

Buttons

The two buttons on this page belong to GTK. Gtk.Button is the plain button every other one is built on, and Gtk.MenuButton is the one whose press opens a menu. Libadwaita ships neither, and the Adwaita stylesheet gives both their GNOME shape.

The buttons libadwaita does ship are on Adwaita Buttons, which covers button content, the split button and the toggle group.

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.

GTK 4 is the reference here, typed by @girs/gtk-4.0. The browser port spells both buttons the way the GIR does — <gtk-button> and <gtk-menu-button>, the same tags @gjsify/gtk-host uses — because a widget is named after the library that owns it and the Adwaita part of these two is the stylesheet. The NativeScript port names them by the same rule, GtkButton and GtkMenuButton. Where a port behaves differently, its own window says so.

A plain button is Gtk.Button. Add a style class to restyle it. .pill makes it rounded and prominent, .circular suits an icon-only button, .suggested-action and .destructive-action colour an affirmative or a dangerous action, and .flat drops the visible background. They compose with each other and mean the same thing everywhere in the toolkit, so a .destructive-action in a header bar reads like one in a dialog.

Gtk.Button
HTML
<div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center;align-items:center;">
<gtk-button label="Pill" pill></gtk-button>
<gtk-button icon="list-add" circular></gtk-button>
<gtk-button label="Suggested" suggested></gtk-button>
<gtk-button label="Delete" destructive></gtk-button>
<gtk-button label="Flat" flat></gtk-button>
</div>
<gtk-button> 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 list-add
label Pill
tooltip not used
disabled not used
flat set
suggested set
destructive set
circular set
pill set
Native TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
import Gtk from 'gi://Gtk?version=4.0';
const box = new Adw.WrapBox({ childSpacing: 12, lineSpacing: 12, halign: Gtk.Align.CENTER });
const pill = new Gtk.Button({ label: 'Pill' });
pill.add_css_class('pill');
box.append(pill);
const circular = new Gtk.Button({ iconName: 'list-add-symbolic' });
circular.add_css_class('circular');
box.append(circular);
const suggested = new Gtk.Button({ label: 'Suggested' });
suggested.add_css_class('suggested-action');
box.append(suggested);
const destructive = new Gtk.Button({ label: 'Delete' });
destructive.add_css_class('destructive-action');
box.append(destructive);
const flat = new Gtk.Button({ label: 'Flat' });
flat.add_css_class('flat');
box.append(flat);
Code
using Gtk 4.0;
using Adw 1;
Adw.WrapBox {
child-spacing: 12;
line-spacing: 12;
halign: center;
Gtk.Button {
label: _("Pill");
styles ["pill"]
}
Gtk.Button {
icon-name: "list-add-symbolic";
styles ["circular"]
}
Gtk.Button {
label: _("Suggested");
styles ["suggested-action"]
}
Gtk.Button {
label: _("Delete");
styles ["destructive-action"]
}
Gtk.Button {
label: _("Flat");
styles ["flat"]
}
}
UI frameworks
TypeScript
// mount(() => <GtkButton />, container) — from '@gjsify/gtk-host/solid'
const GtkButton = () => (
<gtk-box
orientation="horizontal"
spacing={12}
halign="center"
>
<gtk-button
label="Pill"
cssClasses={['pill']}
/>
<gtk-button
iconName="list-add-symbolic"
cssClasses={['circular']}
/>
<gtk-button
label="Suggested"
cssClasses={['suggested-action']}
/>
<gtk-button
label="Delete"
cssClasses={['destructive-action']}
/>
<gtk-button
label="Flat"
cssClasses={['flat']}
/>
</gtk-box>
);
Vue
<!-- GtkButton.vue — mount(GtkButton, container) from '@gjsify/gtk-host/vue' -->
<template>
<gtk-box
orientation="horizontal"
:spacing="12"
halign="center"
>
<gtk-button
label="Pill"
:css-classes="['pill']"
/>
<gtk-button
icon-name="list-add-symbolic"
:css-classes="['circular']"
/>
<gtk-button
label="Suggested"
:css-classes="['suggested-action']"
/>
<gtk-button
label="Delete"
:css-classes="['destructive-action']"
/>
<gtk-button
label="Flat"
:css-classes="['flat']"
/>
</gtk-box>
</template>
TypeScript
// createRoot(container).render(<GtkButton />) — from '@gjsify/gtk-host/react'
const GtkButton = () => (
<gtk-box
orientation="horizontal"
spacing={12}
halign="center"
>
<gtk-button
label="Pill"
cssClasses={['pill']}
/>
<gtk-button
iconName="list-add-symbolic"
cssClasses={['circular']}
/>
<gtk-button
label="Suggested"
cssClasses={['suggested-action']}
/>
<gtk-button
label="Delete"
cssClasses={['destructive-action']}
/>
<gtk-button
label="Flat"
cssClasses={['flat']}
/>
</gtk-box>
);
NativeScript
TypeScript
import { GtkButton, AdwWrapBox } from '@gjsify/adwaita-nativescript';
const box = new AdwWrapBox();
box.childSpacing = 12;
box.lineSpacing = 12;
const pill = new GtkButton();
pill.text = 'Pill';
pill.variant = 'pill';
box.add(pill);
const suggested = new GtkButton();
suggested.text = 'Suggested';
suggested.variant = 'suggested-action';
box.add(suggested);
const destructive = new GtkButton();
destructive.text = 'Delete';
destructive.variant = 'destructive-action';
box.add(destructive);
const flat = new GtkButton();
flat.text = 'Flat';
flat.variant = 'flat';
box.add(flat);
XML
<StackLayout
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adwaita"
orientation="horizontal"
>
<adw:GtkButton
text="Pill"
variant="pill"
/>
<adw:GtkButton
text="Suggested"
variant="suggested"
/>
<adw:GtkButton
text="Delete"
variant="destructive"
/>
<adw:GtkButton
text="Flat"
variant="flat"
/>
</StackLayout>

When a button’s only job is to open a menu, use a menu button. It is the hamburger at the end of a header bar, with no action of its own and every choice in the popover it opens. Libadwaita ships no menu button, so this is Gtk.MenuButton. The Adwaita stylesheet gives it the flat, rounded-square shape.

Gtk.MenuButton
HTML
<gtk-menu-button
icon-name="open-menu"
menu-title="Document"
menu='[{"label":"Save as…"},{"label":"Export"},{"label":"Print"}]'
></gtk-menu-button>
<gtk-menu-button> 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-name open-menu
menu-title Document
menu [{"label":"Save as…"},{"label":"Export"},{"label":"Print"}]
disabled not used
flat not used
circular not used
direction not used
Native TypeScript
TypeScript
import Gio from 'gi://Gio?version=2.0';
import Gtk from 'gi://Gtk?version=4.0';
const menu = new Gio.Menu();
menu.append('Save as…', 'app.save-as');
menu.append('Export', 'app.export');
menu.append('Print', 'app.print');
const button = new Gtk.MenuButton({
iconName: 'open-menu-symbolic',
menuModel: menu,
primary: true, // opens on F10, the way an app menu should
});
button.add_css_class('flat');
Code
using Gtk 4.0;
Gtk.MenuButton {
icon-name: "open-menu-symbolic";
primary: true;
menu-model: primary-menu;
styles ["flat"]
}
menu primary-menu {
section {
item {
label: _("Save as…");
action: "app.save-as";
}
item {
label: _("Export");
action: "app.export";
}
item {
label: _("Print");
action: "app.print";
}
}
}
UI frameworks

No Solid, Vue or React snippet for Gtk.MenuButton: its menu is a Gio.MenuModel, built imperatively.

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 { GtkMenuButton } from '@gjsify/adwaita-nativescript';
import { openMenuSymbolic } from '@gjsify/adwaita-icons/actions';
const button = new GtkMenuButton();
button.iconName = openMenuSymbolic; // an Adwaita symbolic icon (SVG string)
button.menuTitle = 'Document';
button.menuItems = [{ label: 'Save as…' }, { label: 'Export' }, { label: 'Print' }];
// NativeScript has no popover, so the menu opens as the platform action sheet.

No NativeScript XML template for Gtk.MenuButton: GtkMenuButton.menuItems is an array of menu items; an XML attribute is a string.

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.

  • Controls: the entry and the drop down, the other two GTK widgets the Adwaita stylesheet paints.
  • Adwaita Buttons: button content, the split button and the toggle group.
  • Adwaita Storybook: the same widgets in a live component browser you can poke at.