Skip to content

Boxed Lists

Reach for a boxed list whenever you build a settings or detail screen: a set of related rows gathered into one rounded card. The card is a preferences group, and each row is a small self-contained control.

What the preview does not show

The previews run the browser port, so behaviour Libadwaita drives natively (an entry’s apply button, a spin button’s repeat, the press feedback on a row) can be simplified here.

The group draws its rows as one rounded card. Give it a title, a description, and a header-suffix widget (a button or switch pinned to the trailing edge of the header). Stack several groups for a full preferences page.

Adw.PreferencesGroup
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
title: _("Account");
description: _("Manage how this device signs in and syncs.");
header-suffix: Gtk.Button sign_out {
label: _("Sign out");
valign: center;
styles ["flat"]
};
Adw.EntryRow {
title: _("Display name");
text: "Grace Hopper";
}
Adw.SwitchRow {
title: _("Sync over Wi-Fi only");
subtitle: _("Avoid using mobile data for backups");
active: true;
}
Adw.ComboRow {
title: _("Region");
model: Gtk.StringList {
strings [
_("Europe"),
_("Americas"),
_("Asia"),
_("Oceania"),
]
};
selected: 0;
}
}
TypeScript
TypeScript
import { build } from './preferences-group.blp';
const { sign_out } = build();
sign_out.connect('clicked', () => console.log('Sign out'));
Web Components
index.html
HTML
<adw-preferences-group
title="Account"
description="Manage how this device signs in and syncs."
>
<gtk-button
id="sign_out"
label="Sign out"
valign="center"
class="flat"
slot="header-suffix"
></gtk-button>
<adw-entry-row title="Display name" text="Grace Hopper"></adw-entry-row>
<adw-switch-row
title="Sync over Wi-Fi only"
subtitle="Avoid using mobile data for backups"
active
></adw-switch-row>
<adw-combo-row title="Region" selected="0">
<gtk-string-list
strings='["Europe","Americas","Asia","Oceania"]'
slot="model"
></gtk-string-list>
</adw-combo-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const signOut = document.querySelector('#sign_out');
signOut.addEventListener('click', () => console.log('Sign out'));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './preferences-group.blp?shared-tree';
mountSharedTree(tree);
const signOut = document.querySelector('#sign_out');
signOut.addEventListener('click', () => console.log('Sign out'));
NativeScript
views/preferences-group.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
title="Account"
description="Manage how this device signs in and syncs."
>
<adw:PreferencesGroup.header-suffix>
<gtk:Button
id="sign_out"
label="Sign out"
valign="center"
styleClasses="flat"
/>
</adw:PreferencesGroup.header-suffix>
<adw:EntryRow title="Display name" text="Grace Hopper" />
<adw:SwitchRow
title="Sync over Wi-Fi only"
subtitle="Avoid using mobile data for backups"
active="true"
/>
<adw:ComboRow title="Region" selected="0">
<adw:ComboRow.model>
<gtk:StringList strings='["Europe","Americas","Asia","Oceania"]' />
</adw:ComboRow.model>
</adw:ComboRow>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'preferences-group' }) as Adw.PreferencesGroup;
const signOut = group.getViewById<Gtk.Button>('sign_out')!;
signOut.connect('clicked', () => console.log('Sign out'));
app.ts (Blueprint)
TypeScript
import { type Adw, type Gtk } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './preferences-group.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const signOut = group.getViewById<Gtk.Button>('sign_out')!;
signOut.connect('clicked', () => console.log('Sign out'));
Solid
TypeScript
// mount(() => <AdwPreferencesGroup />, container) — from '@gjsify/gtk-host/solid'
const AdwPreferencesGroup = () => (
<adw-preferences-group
title="Account"
description="Manage how this device signs in and syncs."
>
<adw-entry-row
title="Display name"
text="Grace Hopper"
/>
<adw-switch-row
title="Sync over Wi-Fi only"
subtitle="Avoid using mobile data for backups"
active
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwPreferencesGroup.vue — mount(AdwPreferencesGroup, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group
title="Account"
description="Manage how this device signs in and syncs."
>
<adw-entry-row
title="Display name"
text="Grace Hopper"
/>
<adw-switch-row
title="Sync over Wi-Fi only"
subtitle="Avoid using mobile data for backups"
:active="true"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwPreferencesGroup />) — from '@gjsify/gtk-host/react'
const AdwPreferencesGroup = () => (
<adw-preferences-group
title="Account"
description="Manage how this device signs in and syncs."
>
<adw-entry-row
title="Display name"
text="Grace Hopper"
/>
<adw-switch-row
title="Sync over Wi-Fi only"
subtitle="Avoid using mobile data for backups"
active
/>
</adw-preferences-group>
);

The base every row in a boxed list grows from: a list row with a title and nothing more. The title is the text the preferences dialog searches, so give a row one even when you draw the rest yourself. It takes Pango markup unless use-markup is off, an underscore marks a mnemonic with use-underline, and title-selectable lets the user copy it. The action, switch and entry rows all add their own controls on top of this one.

Adw.PreferencesRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.PreferencesRow {
title: _("A plain preferences row");
use-markup: true;
}
}
TypeScript
TypeScript
import Adw from 'gi://Adw?version=1';
const row = new Adw.PreferencesRow({ title: 'A plain preferences row', useMarkup: true });
const group = new Adw.PreferencesGroup();
group.add(row);
Web Components
HTML
<adw-preferences-group>
<!-- use-markup: Whether to use Pango markup for the title label. -->
<adw-preferences-row title="A plain preferences row" use-markup></adw-preferences-row>
</adw-preferences-group>
Solid
TypeScript
// mount(() => <AdwPreferencesRow />, container) — from '@gjsify/gtk-host/solid'
const AdwPreferencesRow = () => (
<adw-preferences-group>
<adw-preferences-row
title="A plain preferences row"
useMarkup
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwPreferencesRow.vue — mount(AdwPreferencesRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-preferences-row
title="A plain preferences row"
:use-markup="true"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwPreferencesRow />) — from '@gjsify/gtk-host/react'
const AdwPreferencesRow = () => (
<adw-preferences-group>
<adw-preferences-row
title="A plain preferences row"
useMarkup
/>
</adw-preferences-group>
);

The general-purpose row: a title with an optional subtitle, plus prefix and suffix slots for extra widgets (an icon on the leading edge, a button or chevron on the trailing edge). Mark it activatable and the whole row responds to a click, which is how you open a detail page.

Adw.ActionRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.ActionRow row {
title: _("Wi-Fi");
subtitle: _("Connected to Highgarden 5GHz");
activatable: true;
[prefix]
Gtk.Image {
icon-name: "network-wireless-symbolic";
}
[suffix]
Gtk.Image {
icon-name: "go-next-symbolic";
}
}
}
TypeScript
TypeScript
import { build } from './action-row.blp';
const { row } = build();
row.connect('activated', () => console.log('Open Wi-Fi settings'));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-action-row
id="row"
title="Wi-Fi"
subtitle="Connected to Highgarden 5GHz"
activatable
>
<gtk-image icon-name="network-wireless-symbolic" slot="prefix"></gtk-image>
<gtk-image icon-name="go-next-symbolic" slot="suffix"></gtk-image>
</adw-action-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('activated', () => console.log('Open Wi-Fi settings'));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './action-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('activated', () => console.log('Open Wi-Fi settings'));
NativeScript
views/action-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
>
<adw:ActionRow
id="row"
title="Wi-Fi"
subtitle="Connected to Highgarden 5GHz"
activatable="true"
>
<adw:ActionRow.prefix>
<gtk:Image iconName="network-wireless-symbolic" />
</adw:ActionRow.prefix>
<adw:ActionRow.suffix>
<gtk:Image iconName="go-next-symbolic" />
</adw:ActionRow.suffix>
</adw:ActionRow>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'action-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ActionRow>('row')!;
row.connect('activated', () => console.log('Open Wi-Fi settings'));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './action-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ActionRow>('row')!;
row.connect('activated', () => console.log('Open Wi-Fi settings'));
Solid
TypeScript
// mount(() => <AdwActionRow />, container) — from '@gjsify/gtk-host/solid'
const AdwActionRow = () => (
<adw-preferences-group>
<adw-action-row
title="Wi-Fi"
subtitle="Connected to Highgarden 5GHz"
activatable
>
<gtk-image
slot="prefix"
iconName="network-wireless-symbolic"
/>
<gtk-button
slot="suffix"
iconName="go-next-symbolic"
cssClasses={['flat']}
valign="center"
/>
</adw-action-row>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwActionRow.vue — mount(AdwActionRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-action-row
title="Wi-Fi"
subtitle="Connected to Highgarden 5GHz"
:activatable="true"
>
<gtk-image
slot="prefix"
icon-name="network-wireless-symbolic"
/>
<gtk-button
slot="suffix"
icon-name="go-next-symbolic"
:css-classes="['flat']"
valign="center"
/>
</adw-action-row>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwActionRow />) — from '@gjsify/gtk-host/react'
const AdwActionRow = () => (
<adw-preferences-group>
<adw-action-row
title="Wi-Fi"
subtitle="Connected to Highgarden 5GHz"
activatable
>
<gtk-image
slot="prefix"
iconName="network-wireless-symbolic"
/>
<gtk-button
slot="suffix"
iconName="go-next-symbolic"
cssClasses={['flat']}
valign="center"
/>
</adw-action-row>
</adw-preferences-group>
);

An action row with a trailing switch, for a single on/off setting. This is the row you will use most in a preferences page.

Adw.SwitchRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.SwitchRow row {
title: _("Automatic updates");
subtitle: _("Download and install updates without asking");
active: true;
}
}
TypeScript
TypeScript
import { build } from './switch-row.blp';
const { row } = build();
row.connect('notify::active', () => console.log('Automatic updates:', row.active));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-switch-row
id="row"
title="Automatic updates"
subtitle="Download and install updates without asking"
active
></adw-switch-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('notify::active', () => console.log('Automatic updates:', row.hasAttribute('active')));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './switch-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('notify::active', () => console.log('Automatic updates:', row.hasAttribute('active')));
NativeScript
views/switch-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
>
<adw:SwitchRow
id="row"
title="Automatic updates"
subtitle="Download and install updates without asking"
active="true"
/>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'switch-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.SwitchRow>('row')!;
row.connect('notify::active', () => console.log('Automatic updates:', row.active));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './switch-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.SwitchRow>('row')!;
row.connect('notify::active', () => console.log('Automatic updates:', row.active));
Solid
TypeScript
// mount(() => <AdwSwitchRow />, container) — from '@gjsify/gtk-host/solid'
const AdwSwitchRow = () => (
<adw-preferences-group>
<adw-switch-row
title="Automatic updates"
subtitle="Download and install updates without asking"
active
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwSwitchRow.vue — mount(AdwSwitchRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-switch-row
title="Automatic updates"
subtitle="Download and install updates without asking"
:active="true"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwSwitchRow />) — from '@gjsify/gtk-host/react'
const AdwSwitchRow = () => (
<adw-preferences-group>
<adw-switch-row
title="Automatic updates"
subtitle="Download and install updates without asking"
active
/>
</adw-preferences-group>
);

Edit a short value in place. The title doubles as the label and placeholder for an inline text field, which suits a name, a hostname or a nickname. Set showApplyButton if the change should be committed explicitly rather than as the user types.

Adw.EntryRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.EntryRow row {
title: _("Display name");
text: "Ada Lovelace";
}
}
TypeScript
TypeScript
import { build } from './entry-row.blp';
const { row } = build();
row.connect('notify::text', () => console.log('Display name:', row.text));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-entry-row
id="row"
title="Display name"
text="Ada Lovelace"
></adw-entry-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('notify::text', () => console.log('Display name:', row.getAttribute('text')));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './entry-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('notify::text', () => console.log('Display name:', row.getAttribute('text')));
NativeScript
views/entry-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
>
<adw:EntryRow id="row" title="Display name" text="Ada Lovelace" />
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'entry-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.EntryRow>('row')!;
row.connect('notify::text', () => console.log('Display name:', row.text));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './entry-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.EntryRow>('row')!;
row.connect('notify::text', () => console.log('Display name:', row.text));
Solid
TypeScript
// mount(() => <AdwEntryRow />, container) — from '@gjsify/gtk-host/solid'
const AdwEntryRow = () => (
<adw-preferences-group>
<adw-entry-row
title="Display name"
text="Ada Lovelace"
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwEntryRow.vue — mount(AdwEntryRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-entry-row
title="Display name"
text="Ada Lovelace"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwEntryRow />) — from '@gjsify/gtk-host/react'
const AdwEntryRow = () => (
<adw-preferences-group>
<adw-entry-row
title="Display name"
text="Ada Lovelace"
/>
</adw-preferences-group>
);

The same row for secrets: it masks what you type and offers a peek toggle to reveal it. Use it anywhere a password or key is edited inline.

Adw.PasswordEntryRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.PasswordEntryRow row {
title: _("Password");
text: "correct-horse-battery";
}
}
TypeScript
TypeScript
import { build } from './password-entry-row.blp';
const { row } = build();
row.connect('notify::text', () => console.log('Password length:', row.text.length));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-password-entry-row
id="row"
title="Password"
text="correct-horse-battery"
></adw-password-entry-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('notify::text', () => console.log('Password length:', row.getAttribute('text')?.length));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './password-entry-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('notify::text', () => console.log('Password length:', row.getAttribute('text')?.length));
NativeScript
views/password-entry-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
>
<adw:PasswordEntryRow
id="row"
title="Password"
text="correct-horse-battery"
/>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'password-entry-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.PasswordEntryRow>('row')!;
row.connect('notify::text', () => console.log('Password length:', row.text.length));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './password-entry-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.PasswordEntryRow>('row')!;
row.connect('notify::text', () => console.log('Password length:', row.text.length));
Solid
TypeScript
// mount(() => <AdwPasswordEntryRow />, container) — from '@gjsify/gtk-host/solid'
const AdwPasswordEntryRow = () => (
<adw-preferences-group>
<adw-password-entry-row
title="Password"
text="correct-horse-battery"
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwPasswordEntryRow.vue — mount(AdwPasswordEntryRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-password-entry-row
title="Password"
text="correct-horse-battery"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwPasswordEntryRow />) — from '@gjsify/gtk-host/react'
const AdwPasswordEntryRow = () => (
<adw-preferences-group>
<adw-password-entry-row
title="Password"
text="correct-horse-battery"
/>
</adw-preferences-group>
);

Pick one option from a fixed list. The row shows an inline drop-down, and the selection is an index into the model you gave it, not the string itself.

Adw.ComboRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.ComboRow row {
title: _("Accent colour");
subtitle: _("Used to highlight selected items");
model: Gtk.StringList {
strings [
_("Blue"),
_("Teal"),
_("Green"),
_("Orange"),
_("Purple"),
]
};
selected: 1;
}
}
TypeScript
TypeScript
import { build } from './combo-row.blp';
const { row } = build();
row.connect('notify::selected', () => console.log(`Accent ${row.selected}`));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-combo-row
id="row"
title="Accent colour"
subtitle="Used to highlight selected items"
selected="1"
>
<gtk-string-list
strings='["Blue","Teal","Green","Orange","Purple"]'
slot="model"
></gtk-string-list>
</adw-combo-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('notify::selected', () => console.log(`Accent ${row.selected}`));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './combo-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('notify::selected', () => console.log(`Accent ${row.selected}`));
NativeScript
views/combo-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
>
<adw:ComboRow
id="row"
title="Accent colour"
subtitle="Used to highlight selected items"
selected="1"
>
<adw:ComboRow.model>
<gtk:StringList strings='["Blue","Teal","Green","Orange","Purple"]' />
</adw:ComboRow.model>
</adw:ComboRow>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'combo-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ComboRow>('row')!;
row.connect('notify::selected', () => console.log(`Accent ${row.selected}`));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './combo-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ComboRow>('row')!;
row.connect('notify::selected', () => console.log(`Accent ${row.selected}`));
Solid
TypeScript
// mount(() => <AdwComboRow />, container) — from '@gjsify/gtk-host/solid'
const AdwComboRow = () => (
<adw-preferences-group>
<adw-combo-row
title="Accent colour"
subtitle="Used to highlight selected items"
model={['Blue', 'Teal', 'Green', 'Orange', 'Purple']}
selected={1}
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwComboRow.vue — mount(AdwComboRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-combo-row
title="Accent colour"
subtitle="Used to highlight selected items"
:model="['Blue', 'Teal', 'Green', 'Orange', 'Purple']"
:selected="1"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwComboRow />) — from '@gjsify/gtk-host/react'
const AdwComboRow = () => (
<adw-preferences-group>
<adw-combo-row
title="Accent colour"
subtitle="Used to highlight selected items"
model={['Blue', 'Teal', 'Green', 'Orange', 'Purple']}
selected={1}
/>
</adw-preferences-group>
);

The model stays live: assign a new one and the row follows — the visible list, the inline value and the chevron, which the row hides while there is nothing to choose between.

TypeScript
const row = document.querySelector('adw-combo-row');
row.model = ['Blue', 'Teal'];
row.selectedValue = 'Teal'; // pick by value; `row.selected` picks by index
row.selectedItem; // → { value: 'Teal', label: 'Teal' }
row.model = [...row.model, { value: 'g', label: 'Green' }];
What the model property accepts

The list is the model property, a Gtk.StringList in the Blueprint above. In code it can also be an array of plain strings, or of { value, label } objects when the value you store differs from the text you show — { label: 'Blue' } on its own is addressed by 'Blue'. Write model before selected in Blueprint: GtkBuilder sets properties in file order, and setting a model resets the selection, so a selected written first ends up on the first item.

That last line above adds one option and leaves the two already on screen alone, so a list that grows does not flicker or lose the reader’s place. Reading row.model hands you a copy, so you can push, filter or rename an item and nothing on screen moves until you assign it back.

The same array is what <gtk-drop-down> takes, under the same model name — and so do Adw.ComboRow on NativeScript and the <AdwComboRow model={…}> prop in React Native, so one model moves between the surfaces without being rewritten.

Gtk.StringList is the other spelling of that same array, and the code windows above use it because it is what a GJS author writes. On GJS it is the real GtkStringList; on the web and NativeScript ports it is an Array subclass, so model = ['Blue', 'Teal'] and model = new Gtk.StringList({ strings: ['Blue', 'Teal'] }) are the same write and either one reads back the same list.

Which widgets take a `model`

model is what this row and the drop-down take, and only they. The widgets whose contents are child widgets — sidebar, tab view, toggle group, view stack, carousel — have no such property, here or in Libadwaita: their items are added one at a time. Keep using what each one already gives you — <adw-sidebar>’s sections, <adw-tab-view>’s appendPage(), or plain child elements in markup for the toggle group, the view stack and the carousel.

Take a number within a range. The row carries an inline spin button with plus and minus steppers and optional decimal digits, which fits settings like a font size or a count.

Adw.SpinRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.SpinRow row {
title: _("Font size");
adjustment: Gtk.Adjustment {
lower: 0;
upper: 100;
value: 16;
step-increment: 1;
};
}
}
TypeScript
TypeScript
import { build } from './spin-row.blp';
const { row } = build();
row.connect('notify::value', () => console.log(`Font size ${row.value}`));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-spin-row id="row" title="Font size">
<gtk-adjustment
lower="0"
upper="100"
value="16"
step-increment="1"
slot="adjustment"
></gtk-adjustment>
</adw-spin-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('notify::value', () => console.log(`Font size ${row.value}`));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './spin-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('notify::value', () => console.log(`Font size ${row.value}`));
NativeScript
views/spin-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
>
<adw:SpinRow id="row" title="Font size">
<adw:SpinRow.adjustment>
<gtk:Adjustment lower="0" upper="100" value="16" stepIncrement="1" />
</adw:SpinRow.adjustment>
</adw:SpinRow>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'spin-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.SpinRow>('row')!;
row.connect('notify::value', () => console.log(`Font size ${row.value}`));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './spin-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.SpinRow>('row')!;
row.connect('notify::value', () => console.log(`Font size ${row.value}`));
Solid
TypeScript
// mount(() => <AdwSpinRow />, container) — from '@gjsify/gtk-host/solid'
const AdwSpinRow = () => (
<adw-preferences-group>
<adw-spin-row
title="Font size"
adjustment={{ lower: 0, upper: 100, value: 16, stepIncrement: 1 }}
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwSpinRow.vue — mount(AdwSpinRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-spin-row
title="Font size"
:adjustment="{ lower: 0, upper: 100, value: 16, stepIncrement: 1 }"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwSpinRow />) — from '@gjsify/gtk-host/react'
const AdwSpinRow = () => (
<adw-preferences-group>
<adw-spin-row
title="Font size"
adjustment={{ lower: 0, upper: 100, value: 16, stepIncrement: 1 }}
/>
</adw-preferences-group>
);

Tuck advanced or grouped settings out of the way. The row discloses its nested rows when expanded, and it can carry an enable switch that gates the whole section.

Adw.ExpanderRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.ExpanderRow row {
title: _("Proxy settings");
subtitle: _("Route traffic through a custom proxy");
expanded: true;
[prefix]
Gtk.Button {
label: _("Test");
}
Adw.EntryRow host_row {
title: _("Host");
text: "proxy.example.com";
}
Adw.SwitchRow {
title: _("Use authentication");
}
}
}
TypeScript
TypeScript
import { build } from './expander-row.blp';
const { row, host_row } = build();
row.connect('notify::expanded', () => host_row.grab_focus());
Web Components
index.html
HTML
<adw-preferences-group>
<adw-expander-row
id="row"
title="Proxy settings"
subtitle="Route traffic through a custom proxy"
expanded
>
<gtk-button label="Test" slot="prefix"></gtk-button>
<adw-entry-row
id="host_row"
title="Host"
text="proxy.example.com"
></adw-entry-row>
<adw-switch-row title="Use authentication"></adw-switch-row>
</adw-expander-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
const host = document.querySelector('#host_row');
row.addEventListener('notify::expanded', () => host.focus());
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './expander-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
const host = document.querySelector('#host_row');
row.addEventListener('notify::expanded', () => host.focus());
NativeScript
views/expander-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
xmlns:gtk="~/gtk"
>
<adw:ExpanderRow
id="row"
title="Proxy settings"
subtitle="Route traffic through a custom proxy"
expanded="true"
>
<adw:ExpanderRow.prefix>
<gtk:Button label="Test" />
</adw:ExpanderRow.prefix>
<adw:EntryRow id="host_row" title="Host" text="proxy.example.com" />
<adw:SwitchRow title="Use authentication" />
</adw:ExpanderRow>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'expander-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ExpanderRow>('row')!;
const host = row.getViewById<Adw.EntryRow>('host_row')!;
row.connect('notify::expanded', () => host.focus());
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './expander-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ExpanderRow>('row')!;
const host = row.getViewById<Adw.EntryRow>('host_row')!;
row.connect('notify::expanded', () => host.focus());
Solid
TypeScript
// mount(() => <AdwExpanderRow />, container) — from '@gjsify/gtk-host/solid'
const AdwExpanderRow = () => (
<adw-preferences-group>
<adw-expander-row
title="Proxy settings"
subtitle="Route traffic through a custom proxy"
expanded
>
<gtk-button
slot="prefix"
label="Test"
/>
<adw-entry-row
title="Host"
text="proxy.example.com"
/>
<adw-switch-row title="Use authentication" />
</adw-expander-row>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwExpanderRow.vue — mount(AdwExpanderRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-expander-row
title="Proxy settings"
subtitle="Route traffic through a custom proxy"
:expanded="true"
>
<gtk-button
slot="prefix"
label="Test"
/>
<adw-entry-row
title="Host"
text="proxy.example.com"
/>
<adw-switch-row title="Use authentication" />
</adw-expander-row>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwExpanderRow />) — from '@gjsify/gtk-host/react'
const AdwExpanderRow = () => (
<adw-preferences-group>
<adw-expander-row
title="Proxy settings"
subtitle="Route traffic through a custom proxy"
expanded
>
<gtk-button
slot="prefix"
label="Test"
/>
<adw-entry-row
title="Host"
text="proxy.example.com"
/>
<adw-switch-row title="Use authentication" />
</adw-expander-row>
</adw-preferences-group>
);

Put an action at the end of a list, such as Add account or Remove. The whole row behaves like a button. It takes an optional start icon and the usual Adwaita style classes (.suggested-action, .destructive-action).

Adw.ButtonRow
Blueprint
Code
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup {
Adw.ButtonRow row {
title: _("Add account");
start-icon-name: "list-add-symbolic";
styles ["suggested-action"]
}
}
TypeScript
TypeScript
import { build } from './button-row.blp';
const { row } = build();
row.connect('activated', () => console.log('Add an account'));
Web Components
index.html
HTML
<adw-preferences-group>
<adw-button-row
id="row"
title="Add account"
start-icon-name="list-add-symbolic"
class="suggested-action"
></adw-button-row>
</adw-preferences-group>
<script type="module">
import '@gjsify/adwaita-web';
const row = document.querySelector('#row');
row.addEventListener('activated', () => console.log('Add an account'));
</script>
main.js (Blueprint)
JavaScript
import { mountSharedTree } from '@gjsify/adwaita-web';
import tree from './button-row.blp?shared-tree';
mountSharedTree(tree);
const row = document.querySelector('#row');
row.addEventListener('activated', () => console.log('Add an account'));
NativeScript
views/button-row.xml
XML
<adw:PreferencesGroup
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:adw="~/adw"
>
<adw:ButtonRow
id="row"
title="Add account"
startIconName="list-add-symbolic"
styleClasses="suggested-action"
/>
</adw:PreferencesGroup>
app.ts
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { Builder } from '@nativescript/core';
const group = Builder.load({ path: '~/views', name: 'button-row' }) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ButtonRow>('row')!;
row.connect('activated', () => console.log('Add an account'));
app.ts (Blueprint)
TypeScript
import { type Adw } from '@gjsify/adwaita-nativescript';
import { build } from '@gjsify/adwaita-nativescript/builder';
import tree from './button-row.blp?shared-tree';
const group = build(tree) as Adw.PreferencesGroup;
const row = group.getViewById<Adw.ButtonRow>('row')!;
row.connect('activated', () => console.log('Add an account'));
Solid
TypeScript
// mount(() => <AdwButtonRow />, container) — from '@gjsify/gtk-host/solid'
const AdwButtonRow = () => (
<adw-preferences-group>
<adw-button-row
title="Add account"
startIconName="list-add-symbolic"
cssClasses={['suggested-action']}
/>
</adw-preferences-group>
);
Vue
Vue
<!-- AdwButtonRow.vue — mount(AdwButtonRow, container) from '@gjsify/gtk-host/vue' -->
<template>
<adw-preferences-group>
<adw-button-row
title="Add account"
start-icon-name="list-add-symbolic"
:css-classes="['suggested-action']"
/>
</adw-preferences-group>
</template>
React
TypeScript
// createRoot(container).render(<AdwButtonRow />) — from '@gjsify/gtk-host/react'
const AdwButtonRow = () => (
<adw-preferences-group>
<adw-button-row
title="Add account"
startIconName="list-add-symbolic"
cssClasses={['suggested-action']}
/>
</adw-preferences-group>
);
  • Buttons: button content, split buttons and toggle groups.
  • Gtk Controls: the standalone entry and drop down that the entry row and combo row are twins of.
  • Gtk Buttons: the style classes these rows reuse.
  • Layout: the Clamp that keeps a boxed list at a readable width.
  • Feedback: the preferences dialog these groups go into.
  • Adwaita Storybook: the same widgets in a component browser you can poke at.