Skip to content

Buttons

Libadwaita adds three button widgets to GTK’s own: button content for an icon plus a label, a split button pairing an action with alternatives, and a toggle group for a single-choice selector. Gtk.Button and Gtk.MenuButton are GTK’s, under Gtk Buttons.

What the preview does not show

The previews run the browser port, so behaviour Libadwaita drives natively (a split button’s popover) can be simplified here. A toggle group’s keyboard navigation is not: see below.

To put an icon and text on a button, make an Adw.ButtonContent its child. Set can-shrink if the label may ellipsize when horizontal space runs short.

Adw.ButtonContent
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Gtk.Button download_button {
child: Adw.ButtonContent content {
label: _("Download");
icon-name: "folder-download-symbolic";
};
styles ["suggested-action", "pill"]
}
TypeScript
TypeScript
import { build } from './button-content.blp';
const { download_button, content } = build();
download_button.connect('clicked', () => {
content.label = 'Downloading…';
});
Web Components
index.html
HTML
<gtk-button id="download_button" class="suggested-action pill">
<adw-button-content
id="content"
label="Download"
icon-name="folder-download-symbolic"
slot="child"
></adw-button-content>
</gtk-button>
<script type="module">
import '@gjsify/adwaita-web';
const button = document.querySelector('#download_button');
const content = document.querySelector('#content');
button.addEventListener('click', () => {
content.setAttribute('label', 'Downloading…');
});
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './button-content.blp?shared-tree';
mountSharedTree(tree);
const button = document.querySelector('#download_button');
const content = document.querySelector('#content');
button.addEventListener('click', () => {
content.setAttribute('label', 'Downloading…');
});
NativeScript
views/button-content.xml
XML
<gtk:Button
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
id="download_button"
styleClasses="suggested-action pill"
>
<gtk:Button.child>
<adw:ButtonContent
id="content"
label="Download"
iconName="folder-download-symbolic"
/>
</gtk:Button.child>
</gtk:Button>
app.ts
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const button = Builder.load({ path: '~/views', name: 'button-content' }) as Gtk.Button;
const content = button.getViewById<Adw.ButtonContent>('content')!;
// The bitmap is pre-coloured, so a suggested button's white icon is pinned here
// rather than inherited from the stylesheet the way GTK inherits `color`.
content.iconColor = '#ffffff';
button.connect('clicked', () => {
content.label = 'Downloading…';
});
app.ts (Blueprint)
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './button-content.blp?shared-tree';
const button = build(tree) as Gtk.Button;
const content = button.getViewById<Adw.ButtonContent>('content')!;
// The bitmap is pre-coloured, so a suggested button's white icon is pinned here
// rather than inherited from the stylesheet the way GTK inherits `color`.
content.iconColor = '#ffffff';
button.connect('clicked', () => {
content.label = 'Downloading…';
});
Solid
TypeScript
// mount(() => <AdwButtonContent />, container) — from '@gjsify/gtk-host/solid'
const AdwButtonContent = () => (
<gtk-button cssClasses={['suggested-action', 'pill']}>
<adw-button-content
label="Download"
iconName="folder-download-symbolic"
/>
</gtk-button>
);
Vue
Vue
<!-- AdwButtonContent.vue — mount(AdwButtonContent, container) from '@gjsify/gtk-host/vue' -->
<template>
<gtk-button :css-classes="['suggested-action', 'pill']">
<adw-button-content
label="Download"
icon-name="folder-download-symbolic"
/>
</gtk-button>
</template>
React
TypeScript
// createRoot(container).render(<AdwButtonContent />) — from '@gjsify/gtk-host/react'
const AdwButtonContent = () => (
<gtk-button cssClasses={['suggested-action', 'pill']}>
<adw-button-content
label="Download"
iconName="folder-download-symbolic"
/>
</gtk-button>
);

Use a split button when one action is the obvious default and the rest are variations on it. Clicking the main half runs the default; the attached arrow opens a menu with the alternatives.

Adw.SplitButton
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.SplitButton split_button {
label: _("Save");
icon-name: "document-save-symbolic";
}
TypeScript
TypeScript
import Gio from 'gi://Gio?version=2.0';
import { build } from './split-button.blp';
const { split_button } = build();
// split_button.add_css_class('flat'); // the header-bar / toolbar variant
const menu = new Gio.Menu();
menu.append('Save as…', 'app.save-as');
menu.append('Export', 'app.export');
menu.append('Print', 'app.print');
split_button.menuModel = menu;
Web Components
index.html
HTML
<adw-split-button
id="split_button"
label="Save"
icon-name="document-save-symbolic"
></adw-split-button>
<script type="module">
import '@gjsify/adwaita-web';
const button = document.querySelector('#split_button');
// button.classList.add('flat'); // the header-bar / toolbar variant
button.menuModel = [
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
];
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './split-button.blp?shared-tree';
mountSharedTree(tree);
const button = document.querySelector('#split_button');
// button.classList.add('flat'); // the header-bar / toolbar variant
button.menuModel = [
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
];
NativeScript
views/split-button.xml
XML
<adw:SplitButton
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
id="split_button"
label="Save"
iconName="document-save-symbolic"
/>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const button = Builder.load({ path: '~/views', name: 'split-button' }) as Adw.SplitButton;
// button.className = 'adw-split-button flat'; // the header-bar / toolbar variant
button.menuModel = [
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
];
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './split-button.blp?shared-tree';
const button = build(tree) as Adw.SplitButton;
// button.className = 'adw-split-button flat'; // the header-bar / toolbar variant
button.menuModel = [
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
];
Solid
TypeScript
// mount(() => <AdwSplitButton />, container) — from '@gjsify/gtk-host/solid'
const AdwSplitButton = () => (
<adw-split-button
label="Save"
menuModel={[
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
]}
/>
);
Vue
Vue
<!-- AdwSplitButton.vue — mount(AdwSplitButton, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-split-button
label="Save"
:menu-model="[
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
]"
/>
</template>
React
TypeScript
// createRoot(container).render(<AdwSplitButton />) — from '@gjsify/gtk-host/react'
const AdwSplitButton = () => (
<adw-split-button
label="Save"
menuModel={[
{ label: 'Save as…', action: 'app.save-as' },
{ label: 'Export', action: 'app.export' },
{ label: 'Print', action: 'app.print' },
]}
/>
);

For a small set of mutually exclusive options, such as a view mode, use a toggle group: a linked row where exactly one toggle is active at a time. Each toggle takes an icon, a label, or both, and the style classes .flat and .round adapt the group for a toolbar or give it a pill shape.

Each toggle carries a name, and the group’s active-name picks the active one by it, so code that switches the view mode says 'grid' instead of counting toggles.

Keyboard and accessibility

A toggle group is a single tab stop, the way Adw.ToggleGroup routes focus: Tab enters on the active toggle and the next Tab leaves the group, while Left and Right move between toggles. Up and Down stay the page’s, as they are upstream. Home and End jump to the ends — that pair is the WAI-ARIA pattern rather than anything GTK does, since GTK’s focus directions have no Home or End.

It announces itself as a radio group with one checked toggle — GTK_ACCESSIBLE_ROLE_RADIO_GROUP upstream — unless the element already carries a role when it connects, which it then keeps; role="tablist" additionally switches the toggles to tabs, the way Adw.InlineViewSwitcher does. Set it in markup or before the element is inserted: it is read once, which is all GTK allows too — GtkAccessible:accessible-role is documented “cannot be changed once set”, and the only setter is class-level.

Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.ToggleGroup toggle_group {
active-name: "list";
Adw.Toggle {
name: "list";
label: _("List");
icon-name: "view-list-symbolic";
}
Adw.Toggle {
name: "grid";
label: _("Grid");
icon-name: "view-grid-symbolic";
}
Adw.Toggle {
name: "pages";
label: _("Pages");
icon-name: "view-paged-symbolic";
}
}
TypeScript
TypeScript
import { build } from './toggle-group.blp';
const { toggle_group } = build();
toggle_group.activeName = 'grid';
Web Components
index.html
HTML
<adw-toggle-group id="toggle_group" active-name="list">
<adw-toggle
name="list"
label="List"
icon-name="view-list-symbolic"
></adw-toggle>
<adw-toggle
name="grid"
label="Grid"
icon-name="view-grid-symbolic"
></adw-toggle>
<adw-toggle
name="pages"
label="Pages"
icon-name="view-paged-symbolic"
></adw-toggle>
</adw-toggle-group>
<script type="module">
import '@gjsify/adwaita-web';
const group = document.querySelector('#toggle_group');
group.activeName = 'grid';
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './toggle-group.blp?shared-tree';
mountSharedTree(tree);
const group = document.querySelector('#toggle_group');
group.activeName = 'grid';
NativeScript
views/toggle-group.xml
XML
<adw:ToggleGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
id="toggle_group"
activeName="list"
>
<adw:Toggle name="list" label="List" iconName="view-list-symbolic" />
<adw:Toggle name="grid" label="Grid" iconName="view-grid-symbolic" />
<adw:Toggle name="pages" label="Pages" iconName="view-paged-symbolic" />
</adw:ToggleGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'toggle-group' }) as Adw.ToggleGroup;
group.activeName = 'grid';
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './toggle-group.blp?shared-tree';
const group = build(tree) as Adw.ToggleGroup;
group.activeName = 'grid';
  • Gtk Buttons: the plain button, its style classes and the menu button.
  • Boxed Lists: rows that embed these buttons and reuse the same style classes.
  • Layout: the containers that arrange buttons within a window.
  • Adwaita Storybook: the same widgets in a live component browser you can poke at.