designing-gnome-ui

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

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "designing-gnome-ui" with this command: npx skills add mhagrelius/dotfiles/mhagrelius-dotfiles-designing-gnome-ui

Designing GNOME UI

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

Core principle: No UI code without design decisions. Pattern selection and quality verification happen before implementation.

Quality layers: Compliance (follows HIG) → Polish (feels premium) → Rigor (handles edge cases)

Companion skill: For app architecture (lifecycle, threading, GSettings, actions, packaging), use developing-gtk-apps .

What's New (libadwaita 1.6-1.8)

Need Widget/API Notes

Exclusive toggles (view mode) AdwToggleGroup

Replaces multiple GtkToggleButton

Loading indicator AdwSpinner

Works with animations disabled

Persistent bottom controls AdwBottomSheet

Music player, persistent actions

Wrapping content (tags) AdwWrapBox

Auto-wraps like text

Inline view switching AdwInlineViewSwitcher

For cards, sidebars

Keyboard shortcuts AdwShortcutsDialog

Replaces deprecated GtkShortcutsWindow

System accent color Automatic Apps follow desktop preference via portal

System fonts AdwStyleManager

Access monospace/document fonts

Deprecations: .dim-label → use .dimmed class

AdwToggleGroup - view mode switching

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) # Show sheet window.set_content(bottom_sheet)

AdwWrapBox - tag display

wrap_box = Adw.WrapBox(spacing=6) for tag in ["Python", "GTK", "GNOME", "libadwaita"]: chip = Gtk.Label(label=tag) chip.add_css_class("chip") # Custom styling wrap_box.append(chip)

System fonts (1.7+) - for code editors, document views

style_manager = Adw.StyleManager.get_default() mono_font = style_manager.get_monospace_font_name() # User's preferred mono font doc_font = style_manager.get_document_font_name() # User's preferred document font

Also available as CSS: --monospace-font-family, --document-font-family

The Process

digraph gnome_ui_process { rankdir=LR; node [shape=box];

"UI Task" -> "1. Context" -> "2. Patterns" -> "3. Details" -> "4. Checklist" -> "Implement";
"4. Checklist" -> "2. Patterns" [label="issues" style=dashed];

}

  • Context: User goal, app type, constraints (screen size, input)

  • Patterns: Select containers, navigation, controls, feedback

  • Details: Typography, spacing, icons, writing style

  • Checklist: Verify compliance, polish, rigor before code

Container Selection

digraph containers { rankdir=TB; node [shape=box];

"Building what?" [shape=diamond];
"AdwApplicationWindow + HeaderBar" [style=filled fillcolor=lightgreen];
"AdwPreferencesWindow" [style=filled fillcolor=lightgreen];
"AdwDialog" [style=filled fillcolor=lightgreen];

"Building what?" -> "AdwApplicationWindow + HeaderBar" [label="main window"];
"Building what?" -> "AdwPreferencesWindow" [label="settings"];
"Building what?" -> "AdwDialog" [label="modal action"];

}

Scenario Default Notes

App window AdwApplicationWindow

  • AdwHeaderBar

Remember user size, start ~800x600

Settings AdwPreferencesWindow

Handles groups, search, subpages

List of items AdwPreferencesGroup with rows Boxed list style

Primary action Single button, header bar end suggested-action class if emphasized

Destructive action destructive-action class Requires undo or confirmation

Navigation Selection

Structure Default Pattern

Single view None needed

2-4 views AdwViewSwitcher in header bar

Many/dynamic views AdwNavigationSplitView (sidebar)

Hierarchical AdwNavigationView (drill-down)

Control Defaults

Need Default Avoid

On/Off AdwSwitchRow

Checkbox for settings

Choose one (few) AdwComboRow

Radio buttons outside dialogs

Choose one (many) AdwComboRow

  • search Long unsearchable dropdowns

Text input AdwEntryRow

Bare GtkEntry

Multiline text GtkTextView

  • card class Bare unstyled text view

Number AdwSpinRow

Text entry for numbers

Date GtkCalendar in popover Text entry for dates

Action in list AdwActionRow

  • suffix button Multiple buttons per row

Search GtkSearchBar

  • toggle button Always-visible search box

Search Bar Pattern

Search bar slides down from header, toggle with button or Ctrl+F

search_bar = Gtk.SearchBar() search_entry = Gtk.SearchEntry() search_bar.set_child(search_entry) search_bar.connect_entry(search_entry) search_bar.set_key_capture_widget(window) # Type-to-search

Toggle button in header bar

search_btn = Gtk.ToggleButton(icon_name="system-search-symbolic") search_btn.set_tooltip_text("Search") search_bar.bind_property("search-mode-enabled", search_btn, "active", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE) header.pack_end(search_btn) toolbar_view.add_top_bar(search_bar)

Form Validation Pattern

Use error CSS class on invalid fields

def validate_entry(row): text = row.get_text() if not text or len(text) < 3: row.add_css_class("error") row.set_tooltip_text("Name must be at least 3 characters") return False row.remove_css_class("error") row.set_tooltip_text("") return True

name_row.connect("changed", lambda r: validate_entry(r))

Validation timing: On change for format checks, on focus-out for expensive checks, on submit for final validation.

List Widget Selection

Content Widget Why

Settings/preferences AdwPreferencesGroup

Boxed list style, handles rows

Navigation list (sidebar) GtkListBox

Selection support, activatable rows

Large/dynamic data GtkListView

Virtual scrolling, performance

Grid of items GtkGridView

Thumbnail grids, icon views

Selection modes: Use Gtk.SingleSelection for navigation, Gtk.MultiSelection for bulk actions. Toggle selection mode with header bar button + action bar for bulk operations. See reference for code patterns.

Iconography

Rules:

  • Symbolic icons only (outline, monochrome) - never full-color in UI

  • Source from GNOME Icon Library (icon-library app)

  • Header bar: icon-only buttons, always add tooltips

  • Naming: action-object-symbolic (e.g., list-add-symbolic )

  • Dynamic icons: Update icon name based on state (e.g., user-trash-symbolic → user-trash-full-symbolic )

Action Icon

Add/New list-add-symbolic

Delete user-trash-symbolic

Settings emblem-system-symbolic

Menu open-menu-symbolic

Search system-search-symbolic

Edit document-edit-symbolic

Back go-previous-symbolic

Drill-down go-next-symbolic

Sync emblem-synchronizing-symbolic

Offline network-offline-symbolic

Warning dialog-warning-symbolic

Error dialog-error-symbolic

Select mode selection-mode-symbolic

Check/Done emblem-ok-symbolic

Close window-close-symbolic

Refresh view-refresh-symbolic

Feedback Selection

digraph feedback { rankdir=TB; node [shape=box];

"What happened?" [shape=diamond];
"Transient or persistent?" [shape=diamond];
"AdwToast" [style=filled fillcolor=lightgreen label="AdwToast (default)"];
"AdwBanner" [style=filled fillcolor=lightyellow];
"AdwDialog" [style=filled fillcolor=lightpink];
"Progress/Spinner" [style=filled fillcolor=lightblue];

"What happened?" -> "Transient or persistent?" [label="state/error"];
"What happened?" -> "AdwDialog" [label="needs decision"];
"What happened?" -> "Progress/Spinner" [label="ongoing operation"];
"Transient or persistent?" -> "AdwToast" [label="transient event"];
"Transient or persistent?" -> "AdwBanner" [label="persistent state"];

}

Scenario Default Details

Action done AdwToast

Short message, optional undo

Destructive action AdwToast

  • undo Prefer over confirmation dialog

Error (recoverable) AdwToast

Brief, auto-retry silently

Error (blocking) AdwDialog

Explain problem and required fix

Persistent state AdwBanner

Offline, degraded mode, auth required

Needs decision AdwDialog

Conflicts, irreversible actions

Short wait (<5s) AdwSpinner

No progress bar

Long operation (>30s) Progress bar + text "13 of 42 processed"

Error escalation: Toast (transient) → Banner (persists) → Dialog (requires action)

  • Network blip: Toast, auto-retry

  • Prolonged offline: Banner with "Retry" button

  • Auth expired: Dialog + Banner until resolved

Dialog rules:

  • Cancel button first (left), action button last (right)

  • Specific verbs ("Delete", "Save"), never "OK" or "Yes"

  • Destructive actions use destructive-action style

Context menus: Use GtkPopoverMenu for right-click actions (remove, rename, properties). Keep menus short; move complex actions to dialogs.

Empty State Pattern

Show placeholder when list is empty

empty_state = Adw.StatusPage( icon_name="folder-symbolic", title="No Projects", description="Create a project to get started" ) create_btn = Gtk.Button(label="Create Project") create_btn.add_css_class("pill") create_btn.add_css_class("suggested-action") empty_state.set_child(create_btn)

Use stack to switch between list and empty state

stack.add_named(list_view, "content") stack.add_named(empty_state, "empty") stack.set_visible_child_name("empty" if model.get_n_items() == 0 else "content")

Quality Checklist

Create TodoWrite items for each applicable check before implementing.

Layer 1: Compliance

  • Correct container type and header bar structure

  • Navigation pattern matches content structure

  • Standard widgets used (not custom where native exists)

  • Symbolic icons from GNOME Icon Library

  • Typography uses style classes (title-1 , heading , body , caption )

  • Libadwaita spacing defaults (no custom margins)

  • Header capitalization for labels, sentence for descriptions

Layer 2: Polish

  • Clear visual hierarchy - important elements prominent

  • Controls and text properly aligned

  • Consistent patterns throughout

  • Empty states have placeholder page (icon + message + action)

  • Loading states show spinner/skeleton, never frozen UI

  • Smooth resize and view transitions

  • Comfortable density - not cramped, not sparse

Layer 3: Rigor

  • All controls keyboard-accessible (Tab, Enter, Space)

  • All elements have accessible names for screen readers

  • Works with high contrast (GTK_THEME=Adwaita:hc )

  • Works with 200% text scaling

  • Error handling for every input/action

  • Edge cases handled (empty lists, long text, missing data)

  • Destructive actions have undo where possible

  • Responsive: works at 800x600, adapts to larger

Accessibility Quick Check

Test high contrast

GTK_THEME=Adwaita:hc ./myapp

Test large text (set in GNOME Settings > Accessibility first)

Test with screen reader

orca & ./myapp

Keyboard-only: unplug mouse, navigate entire app with Tab/Enter/Space

Code: Set accessible labels for icon-only buttons and images:

button.update_property([Gtk.AccessibleProperty.LABEL], ["Add new item"]) image.update_property([Gtk.AccessibleProperty.LABEL], ["Project thumbnail"])

Red Flags - STOP

  • Custom styling where libadwaita has a pattern

  • Multiple "suggested" or "destructive" buttons per view

  • Confirmation dialogs for reversible actions (use undo)

  • Text over images or textured backgrounds

  • Non-GNOME icons without strong justification

  • Missing tooltips on icon-only header bar buttons

  • Generic labels ("OK", "Yes", "No", "Submit")

  • Frozen UI during operations (missing loading states)

Non-GTK Apps (Qt/PySide6)

When styling Qt apps for GNOME:

  • Use Adwaita-qt or manual QSS matching Adwaita colors

  • Follow same patterns conceptually (header bar → toolbar, etc.)

  • Match spacing, typography scale, and icon style

  • Test alongside native GNOME apps for consistency

Reference Files

Need File

Basic UI patterns gnome-hig-reference.md

Advanced patterns gnome-advanced-patterns.md

gnome-hig-reference.md - Read for most apps:

  • Container, navigation, control, feedback patterns with code

  • Search bar, form validation, filter models, grid views, selection modes

  • File chooser dialogs, dark/light mode, responsive breakpoints

  • Primary menu structure, About dialog, Shortcuts window

  • Typography, writing style, CSS color variables, common mistakes

  • Accessibility testing commands (high contrast, screen reader)

  • Phone/tablet breakpoints, adaptive layouts

gnome-advanced-patterns.md - Read when building:

  • Drag & drop (reordering, file drops, cross-widget DnD)

  • Undo/Redo (command pattern, history management)

  • Tabs (AdwTabView, multi-document apps)

  • System notifications (GNotification vs Toast)

  • Media display (image viewers, video controls, pinch-to-zoom gestures)

  • Split/Paned views (resizable panels)

  • Welcome/Onboarding (first-run, feature callouts)

  • Popovers (tool palettes, color pickers)

  • Keyboard shortcuts (mnemonics, shortcut controllers)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

dotnet-10-csharp-14

No summary provided by upstream source.

Repository SourceNeeds Review
General

building-tui-apps

No summary provided by upstream source.

Repository SourceNeeds Review
General

working-with-aspire

No summary provided by upstream source.

Repository SourceNeeds Review
General

building-mcp-servers

No summary provided by upstream source.

Repository SourceNeeds Review