Skip to content
designing-gnome-ui logo

Designing GNOME UI

designing-gnome-ui

Use when designing, implementing, or modifying UI for GNOME apps; before writing UI code; when reviewing existing UI for HIG compliance; when working with GTK 4/libadwaita or styling Qt/PySide6 for GNOME

SKILL.md

Full skill instructions

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)

NeedWidget/​APINotes
Exclusive toggles (view mode)AdwToggleGroupReplaces multiple GtkToggleButton
Loading indicatorAdwSpinnerWorks with animations disabled
Persistent bottom controlsAdwBottomSheetMusic player, persistent actions
Wrapping content (tags)AdwWrapBoxAuto-wraps like text
Inline view switchingAdwInlineViewSwitcherFor cards, sidebars
Keyboard shortcutsAdwShortcutsDialogReplaces deprecated GtkShortcutsWindow
System accent colorAutomaticApps follow desktop preference via portal
System fontsAdwStyleManagerAccess 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];
}
  1. Context: User goal, app type, constraints (screen size, input)
  2. Patterns: Select containers, navigation, controls, feedback
  3. Details: Typography, spacing, icons, writing style
  4. 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"];
}
ScenarioDefaultNotes
App windowAdwApplicationWindow + AdwHeaderBarRemember user size, start ~800x600
SettingsAdwPreferencesWindowHandles groups, search, subpages
List of itemsAdwPreferencesGroup with rowsBoxed list style
Primary actionSingle button, header bar endsuggested-action class if emphasized
Destructive actiondestructive-action classRequires undo or confirmation

Navigation Selection

StructureDefault Pattern
Single viewNone needed
2-4 viewsAdwViewSwitcher in header bar
Many/​dynamic viewsAdwNavigationSplitView (sidebar)
HierarchicalAdwNavigationView (drill-down)

Control Defaults

NeedDefaultAvoid
On/​OffAdwSwitchRowCheckbox for settings
Choose one (few)AdwComboRowRadio buttons outside dialogs
Choose one (many)AdwComboRow + searchLong unsearchable dropdowns
Text inputAdwEntryRowBare GtkEntry
Multiline textGtkTextView + card classBare unstyled text view
NumberAdwSpinRowText entry for numbers
DateGtkCalendar in popoverText entry for dates
Action in listAdwActionRow + suffix buttonMultiple buttons per row
SearchGtkSearchBar + toggle buttonAlways-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

ContentWidgetWhy
Settings/​preferencesAdwPreferencesGroupBoxed list style, handles rows
Navigation list (sidebar)GtkListBoxSelection support, activatable rows
Large/​dynamic dataGtkListViewVirtual scrolling, performance
Grid of itemsGtkGridViewThumbnail 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)
ActionIcon
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
Syncemblem-synchronizing-symbolic
Offlinenetwork-offline-symbolic
Warningdialog-warning-symbolic
Errordialog-error-symbolic
Select modeselection-mode-symbolic
Check/​Doneemblem-ok-symbolic
Closewindow-close-symbolic
Refreshview-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"];
}
ScenarioDefaultDetails
Action doneAdwToastShort message, optional undo
Destructive actionAdwToast + undoPrefer over confirmation dialog
Error (recoverable)AdwToastBrief, auto-retry silently
Error (blocking)AdwDialogExplain problem and required fix
Persistent stateAdwBannerOffline, degraded mode, auth required
Needs decisionAdwDialogConflicts, irreversible actions
Short wait (<5s)AdwSpinnerNo 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

NeedFile
Basic UI patternsgnome-hig-reference.md
Advanced patternsgnome-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)

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View