skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
mhagrelius/dotfiles100 installs

designing-gnome-ui

Use for every user-visible change in a GNOME app, new or existing, whether or not the request uses design vocabulary. Trigger on the repo rather than the wording — if it depends on gtk4/libadwaita (or is a GNOME-targeted Qt/PySide6 app), anything a user can see or interact with goes through this skill, including requests phrased purely as symptoms ("can I right-click to delete", "the window opens too small", "reordering doesn't work"). Covers choosing widgets, icons, dialogs, menus, sidebars, navigation, and empty states; interaction bugs in context menus, drag-and-drop reordering, window sizing, layout, duplicate affordances, and controls missing or inert at launch; and HIG review. Complements developing-gtk-apps, which owns architecture, threading, language syntax, and build/test plumbing.

How do I install this agent skill?

npx skills add https://github.com/mhagrelius/dotfiles --skill designing-gnome-ui
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The provided skill contains no files, scripts, or instructions. No security threats were identified because there is no content to evaluate.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

  • Runlayerpass

    3 files scanned · No issues

  • ZeroLeakspass

    Score: 93/100 · 2 sections analyzed

What does this agent skill do?

Designing GNOME UI

Design GNOME UIs that are HIG-compliant, polished, and user-centered.

Core principle: No UI code without design decisions. Pattern selection happens before implementation.

Companion skill: For app architecture (lifecycle, threading, GSettings, actions, list-model/factory code, packaging), use developing-gtk-apps. Snippets in this skill are Python (PyGObject) and illustrate which widget and how it fits together; for Vala or Rust syntax, use that skill's language branch files.

Subagents: a subagent given UI work loads no skills on its own — its prompt must tell it to invoke designing-gnome-ui (and developing-gtk-apps for plumbing).

What's Current (libadwaita 1.9, GTK 4.22)

Training data reaches for APIs that are now deprecated. Write the current column, every time:

Deprecated (when)Current (since)
AdwPreferencesWindow (1.6)AdwPreferencesDialog (1.5)
AdwMessageDialog (1.6), GtkMessageDialog (4.10)AdwAlertDialog (1.5)
AdwAboutWindow (1.6)AdwAboutDialog (1.5)
AdwLeaflet, AdwFlap, AdwSqueezer, AdwViewSwitcherTitle (all 1.4)AdwBreakpoint + AdwNavigationSplitView/AdwNavigationView/AdwOverlaySplitView, AdwToolbarView (all 1.4)
GtkShortcutsWindow (4.18)AdwShortcutsDialog (1.8)
GtkFileChooserDialog/Native (4.10)GtkFileDialog (4.10, async)
GtkColorChooserWidget/Button (4.10)GtkColorDialog + GtkColorDialogButton (4.10)
GtkVolumeButton (4.10)GtkScaleButton or GtkScale
GtkSpinner (in Adw apps)AdwSpinner (1.6) — works with animations disabled
.dim-label CSS class.dimmed
@accent_color named colors, @define-colorvar(--accent-color) CSS variables

Why the *Window → *Dialog shift: AdwDialog (1.5) presents adaptively — a centered dialog on desktop, a bottom sheet on narrow/mobile screens — and attaches with dialog.present(parent) instead of transient_for. Every window-based dialog type was retired in its favor; when you meet a new Adw*Window/Adw*Dialog pair, the *Dialog one is current.

Widgets newer than training data (verify the API, don't guess it):

NeedWidget (since)
Exclusive toggles (view mode)AdwToggleGroup + AdwToggle (1.7)
Persistent bottom controls (player)AdwBottomSheet (1.6)
Wrapping content (tag chips)AdwWrapBox (1.7) — child-spacing/line-spacing, no spacing property
Inline view switching (cards, sidebars)AdwInlineViewSwitcher (1.7)
Full-width button in boxed listAdwButtonRow (1.6)
Sectioned sidebarAdwSidebar + AdwSidebarSection/AdwSidebarItem (1.9)
Sidebar that switches an AdwViewStackAdwViewSwitcherSidebar (1.9)
Keyboard shortcuts dialogAdwShortcutsDialog (1.8)
System monospace/document fontsAdwStyleManager get_monospace_font_name()/get_document_font_name() (1.7); CSS --monospace-font-family, --document-font-family
System accent colorAutomatic via portal; --accent-bg-color etc.
# AdwToggleGroup - view mode switching (note: Adw.Toggle objects, not buttons)
toggle_group = Adw.ToggleGroup()
toggle_group.add(Adw.Toggle(icon_name="view-grid-symbolic", name="grid"))
toggle_group.add(Adw.Toggle(icon_name="view-list-symbolic", name="list"))
toggle_group.connect("notify::active-name", lambda g, p: set_view(g.get_active_name()))
header.pack_start(toggle_group)

# AdwBottomSheet - music player controls
bottom_sheet = Adw.BottomSheet()
bottom_sheet.set_content(main_content)
bottom_sheet.set_sheet(player_controls)
bottom_sheet.set_open(True)
window.set_content(bottom_sheet)

# AdwWrapBox - tag display (child_spacing/line_spacing, NOT spacing)
wrap_box = Adw.WrapBox(child_spacing=6, line_spacing=6)
for tag in ["Python", "GTK", "GNOME"]:
    wrap_box.append(Gtk.Label(label=tag))

Verify, Then Claim

A widget or version claim is confirmed by the installed introspection data, not by memory: /usr/share/gir-1.0/Adw-1.gir and Gtk-4.0.gir carry version="1.x" (since) and deprecated-version="1.x" attributes on each class — parse them (attributes span multiple lines; plain grep on the class name misses them). A CSS class is confirmed by the shipped stylesheet: gresource extract /usr/lib/x86_64-linux-gnu/libadwaita-1.so.0 /org/gnome/Adwaita/styles/default-light-yaru-default.css (path varies by distro patching; gresource list shows what's there). Presence in the stylesheet proves a class exists; only the GIR/docs settle whether it is current or a deprecated alias (.dim-label still ships beside .dimmed). An icon name is confirmed by the installed theme:

find /usr/share/icons/Adwaita -name 'NAME-symbolic.svg' | head -1

Some icons ship inside GTK/libadwaita gresources, so an empty result is a strong signal rather than proof — gtk4-icon-browser is the exhaustive check.

Container Selection

ScenarioDefaultNotes
App windowAdwApplicationWindow + AdwHeaderBar in AdwToolbarViewRemember user size, start ~800x600
SettingsAdwPreferencesDialogPages, groups, search built in; present(parent)
List of settings/itemsAdwPreferencesGroup with rowsBoxed list style
Modal action / decisionAdwDialog / AdwAlertDialogAdaptive; bottom sheet on narrow screens
Primary actionSingle button, header bar endsuggested-action class; one per view
Destructive actiondestructive-action classPair with undo or confirmation

Navigation Selection

StructureDefault Pattern
Single viewNone needed
2-4 viewsAdwViewSwitcher in header bar + AdwViewSwitcherBar below breakpoint
Many/dynamic viewsAdwNavigationSplitView with AdwSidebar (1.9) or GtkListBox.navigation-sidebar
View switching via sidebarAdwViewSwitcherSidebar (1.9)
Hierarchical (drill-down)AdwNavigationView
Utility pane that overlays when narrowAdwOverlaySplitView

Adaptivity comes from AdwBreakpoint setters on the window (max-width: 600sp → set collapsed), never from swapping widget trees by hand.

Control Defaults

NeedDefaultInstead of
On/OffAdwSwitchRowCheckbox for settings
Choose one (few)AdwComboRowRadio buttons outside dialogs
Choose one (many)AdwComboRow + enable-searchLong unsearchable dropdowns
Text inputAdwEntryRowBare GtkEntry in lists
Multiline textGtkTextView in ScrolledWindow + card classBare unstyled text view
NumberAdwSpinRowText entry for numbers
One primary action with related variants (a + that can create several kinds of thing)AdwSplitButton — main action on the button, siblings in menu-modelA single button that silently picks one variant
Button in a boxed listAdwButtonRow (1.6)Hand-styled full-width GtkButton
Action in listAdwActionRow + one suffix buttonMultiple buttons per row
SearchGtkSearchBar + header toggle, set_key_capture_widget(window)Always-visible search box

List Widget Selection

ContentWidgetTie-breaker
Settings/preferencesAdwPreferencesGroupStatic rows, boxed style
Navigation/selection listGtkListBoxRow widgets, .navigation-sidebar class, fine under ~hundreds of rows
Large/dynamic dataGtkListViewRecycled widgets via factory — required for thousands of rows
Grid of itemsGtkGridViewSame factory model as ListView

Selection: Gtk.SingleSelection for navigation, Gtk.MultiSelection behind an explicit selection mode (header toggle + GtkActionBar for bulk actions). Factory/model code lives in developing-gtk-apps.

Iconography

Symbolic icons only (-symbolic, monochrome). Header bar buttons are icon-only with tooltips. Icons beyond the system theme (browse the GNOME Icon Library app) must be bundled as resources — a bare icon_name string only resolves from the installed theme.

ActionIcon (verified in Adwaita theme)
Add/Newlist-add-symbolic
Deleteuser-trash-symbolic
Settingsemblem-system-symbolic
Menuopen-menu-symbolic
Searchsystem-search-symbolic
Editdocument-edit-symbolic
Backgo-previous-symbolic
Drill-downgo-next-symbolic
Offlinenetwork-offline-symbolic
Warning / Errordialog-warning-symbolic / dialog-error-symbolic
Select modeselection-mode-symbolic
Check/Doneobject-select-symbolic
Closewindow-close-symbolic
Refresh/Syncview-refresh-symbolic
Open / Savedocument-open-symbolic / document-save-symbolic
Copyedit-copy-symbolic
Find in contentedit-find-symbolic
Linkinsert-link-symbolic
Attachmail-attachment-symbolic
Toggle sidebarsidebar-show-symbolic
New folderfolder-new-symbolic
Overflow/Moreview-more-symbolic
Sortview-sort-ascending-symbolic
Move up / downgo-up-symbolic / go-down-symbolic
Remove from listlist-remove-symbolic
Favoritestar-new-symbolic
Expand/Collapsepan-down-symbolic
Playmedia-playback-start-symbolic

Plausible-sounding names are routinely absent (chain-link-, attach-, dock-left-, view-sidebar-start-, emblem-ok-symbolic all fail on this system) — run any name not in this table through the find check in "Verify, Then Claim" before using it.

Designing Against Platform Limits

Some controls cannot work on GNOME Wayland without a companion shell extension: positioning a window, keeping it above others, hiding it from the dock, grabbing a global shortcut. The design question is what the UI does when the capability is absent — and the answer is never "look like it worked".

StateDoNot
Capability unavailableShow the control insensitive, with a tooltip naming what is missingLeave it interactive; a toggle that latches while nothing happens reads as a bug in your app
Capability unavailableKeep the control presentHide it — a control that appears and disappears between sessions is harder to learn than one that greys out
Degraded, not brokenSay what still works ("content and size still persist")Imply total failure
pin_button.set_sensitive(available)
pin_button.set_tooltip_text(
    "Keep on Top" if available else
    "Keep on Top needs the … extension — Wayland does not let apps raise "
    "their own windows")

Architecture for this lives in developing-gtk-apps (gnome-shell-companion-reference.md).

Feedback Selection

ScenarioDefaultDetails
Action doneAdwToastShort message, optional button
Destructive actionAdwToast + Undo buttonPrefer over confirmation dialog
Error (recoverable)AdwToastBrief, auto-retry silently
Error (blocking) / needs decisionAdwAlertDialogCancel first, specific verb last, DESTRUCTIVE appearance
Persistent state (offline, auth)AdwBannerTop of content, optional button
Data not being savedAdwBanner, and keep it upOngoing condition, not an event. A toast is missed while typing, and the cost of missing it is lost work. Log once, show until fixed
Capability missing (needs a shell extension)Insensitive control + tooltipNever a dialog on startup; it is a limit, not an error
Short wait (<5s)AdwSpinnerNo progress bar
Long operationGtkProgressBar + text"13 of 42 processed"
Empty listAdwStatusPageIcon + title + pill+suggested-action button
Event while backgroundedGNotificationNot toasts — those need the window visible

Escalation: Toast (transient) → Banner (persists) → Dialog (requires action).

Typography & Color

Style classes, not custom CSS: title-1…title-4, heading, body, caption, caption-heading, monospace, numeric, dimmed — and card, pill, flat, boxed-list, navigation-sidebar, toolbar, osd, error/warning/success (all present in the 1.9 stylesheet). Colors via var(--accent-color)-style variables only — full table in gnome-hig-reference.md.

Writing: header capitalization for buttons/menus/titles, sentence capitalization for descriptions/switch labels, no trailing periods, verbs not "OK". Details in gnome-hig-reference.md.

Definition of Done

The design is implemented when the assembled window has been rendered and looked at, and every line checks out against the code.

Render first. Launch the app (or a screenshot harness — developing-gtk-apps has the run/render plumbing) and inspect the actual window before calling anything done; compiling and passing tests draw zero frames. Check in the render:

  • Close/minimise/maximise are visible in every pane state — libadwaita puts window controls on the outermost header bar, so show-end-title-buttons(false) on the wrong pane's header removes them entirely.
  • The shortcuts dialog shows its accelerators — accelerator strings in code are plain <Control>n; XML-escaped entities (&lt;Control&gt;n) belong only inside .ui files.
  • Sidebars/panes start in their intended state — set the split view's show-sidebar explicitly and bind the toggle from it; a sync_create bind from a default-inactive toggle hides the sidebar at launch.
  • Content fills the window — a custom widget sets vexpand/hexpand on itself; a scroller expanding inside a 30px-tall parent means the parent lacks the flag.

Then line checks:

  • Every Adw/Gtk class used appears non-deprecated in the installed GIR (the "What's Current" table has the swaps; anything unfamiliar goes through "Verify, Then Claim").
  • Dialogs are AdwDialog subclasses presented with present(parent).
  • Windows that can go narrow have AdwBreakpoints driving collapsed/switcher-bar changes.
  • Colors and fonts come from var(--…) variables or style classes — zero hardcoded hex values.
  • Text uses .dimmed, never .dim-label.
  • Every icon-only button has tooltip_text; every icon name either exists in the Adwaita theme or is bundled.
  • Each user action has exactly one affordance in the visible view — count across header bar, sidebar, and content together (a header +, a sidebar pill, and a content pill for the same "New" is two too many), and at most one empty state is on screen at a time.
  • At most one suggested-action per view and destructive actions carry undo or an AdwAlertDialog confirmation.
  • Controls for capabilities the platform may not provide are insensitive with an explanatory tooltip, never silently inert.
  • Failures that lose user data raise a persistent banner, not a toast.
  • Labels follow the capitalization table (header caps for buttons/menus, sentence caps for descriptions).

Non-GTK Apps (Qt/PySide6)

No maintained Adwaita Qt theme exists — style via QSS using the color variables above, map header bar → fixed toolbar, and reuse this skill's pattern/spacing/typography decisions. Test beside a native GNOME app.

Reference Files

The question in front of youRead
"How is this pattern wired?" — the AdwAlertDialog response contract (who closes the dialog, the Enter path), AdwSidebar sections/selection/context menus, which containers already scroll, theming GtkTextTag colors for dark mode, search, file dialogs, breakpoints, menus, writing style, the color-variable tablegnome-hig-reference.md
"How do I build drag & drop / tabs (AdwTabView) / system notifications / zoom gestures / paned views / an onboarding carousel / widget-scoped shortcuts?"gnome-advanced-patterns.md

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/mhagrelius/dotfiles/designing-gnome-ui">View designing-gnome-ui on skillZs</a>