Skip to content
Back to skills

Designing Gnome Ui

ASecurity

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

  • 666 stars
  • 0 votes
  • 3 copies
  • 14 views
  • Added February 8, 2026
designpythongobashnodetestingapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill designing-gnome-ui --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Designing Gnome Ui?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Designing Gnome Ui
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-designing-gnome-ui/badge)](https://www.skillsdirectory.com/skills/majiayu000-designing-gnome-ui)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: designing-gnome-ui
description: 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
---

# 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

```python
# 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

```dot
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

```dot
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

```python
# 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

```python
# 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

```dot
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

```python
# 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

```bash
# 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:
```python
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)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.