macos-hig-designer

📁 designnotdrum/skills 📅 12 days ago
0
总安装量
10
周安装量
安装命令
npx skills add https://github.com/designnotdrum/skills --skill macos-hig-designer

Agent 安装分布

opencode 8
claude-code 8
codex 8
gemini-cli 5
cursor 5
antigravity 4

Skill 文档

macOS HIG Designer

Design native macOS applications following Apple’s Human Interface Guidelines with macOS Tahoe’s Liquid Glass design system.

Workflow Decision Tree

User Request
    │
    ├─► "Review my macOS UI code"
    │       └─► Run HIG Compliance Check (Section 11)
    │           └─► Report violations with fixes
    │
    ├─► "Modernize this macOS code"
    │       └─► Identify deprecated APIs
    │           └─► Apply Modern API Replacements (Section 10)
    │
    └─► "Build [feature] for macOS"
            └─► Design with HIG principles first
                └─► Implement with modern SwiftUI patterns

1. Design Principles (macOS Tahoe)

Three Core Tenets

Principle Description Implementation
Hierarchy Visual layers through Liquid Glass translucency Use .glassEffect(), materials, and depth
Harmony Concentric alignment between hardware/software Round corners, consistent radii, flowing shapes
Consistency Platform conventions that adapt to context Follow standard patterns, respect user preferences

Liquid Glass Philosophy

Liquid Glass combines transparency, reflection, refraction, and fluidity with a frosted aesthetic:

// macOS Tahoe Liquid Glass effect
.glassEffect()                    // Primary Liquid Glass material
.glassEffect(.regular.tinted)     // Tinted variant (26.1+)

// Pre-Tahoe fallback
.background(.ultraThinMaterial)
.background(.regularMaterial)
.background(.thickMaterial)

When to use Liquid Glass:

  • Sidebars, toolbars, and navigation chrome
  • Floating panels and popovers
  • Dock and widget backgrounds
  • System-level UI elements

When NOT to use:

  • Primary content areas (documents, media)
  • Dense data displays (tables, lists with many items)
  • Text-heavy interfaces where readability is critical

2. Navigation Patterns

NavigationSplitView (Primary Pattern)

Three-column layout for document-based and content-heavy apps:

struct ContentView: View {
    @State private var selection: Item.ID?
    @State private var columnVisibility: NavigationSplitViewVisibility = .all

    var body: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            // Sidebar (source list)
            List(items, selection: $selection) { item in
                NavigationLink(value: item) {
                    Label(item.title, systemImage: item.icon)
                }
            }
            .navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
        } content: {
            // Content column (optional middle)
            ContentListView(selection: selection)
        } detail: {
            // Detail view
            DetailView(item: selectedItem)
        }
    }
}

Sidebar Patterns

// Source list with sections
List(selection: $selection) {
    Section("Library") {
        ForEach(libraryItems) { item in
            Label(item.name, systemImage: item.icon)
                .tag(item)
        }
    }

    Section("Collections") {
        ForEach(collections) { collection in
            Label(collection.name, systemImage: "folder")
                .tag(collection)
                .badge(collection.count)
        }
    }
}
.listStyle(.sidebar)

Inspector Panel (Trailing Sidebar)

struct DocumentView: View {
    @State private var showInspector = true

    var body: some View {
        MainContentView()
            .inspector(isPresented: $showInspector) {
                InspectorView()
                    .inspectorColumnWidth(min: 200, ideal: 250, max: 400)
            }
            .toolbar {
                ToolbarItem {
                    Button {
                        showInspector.toggle()
                    } label: {
                        Label("Inspector", systemImage: "sidebar.trailing")
                    }
                }
            }
    }
}

3. Window Management

Window Configuration

@main
struct MyApp: App {
    var body: some Scene {
        // Main document window
        WindowGroup {
            ContentView()
        }
        .windowStyle(.automatic)
        .windowToolbarStyle(.unified)
        .defaultSize(width: 900, height: 600)
        .defaultPosition(.center)

        // Settings window
        Settings {
            SettingsView()
        }

        // Utility window
        Window("Inspector", id: "inspector") {
            InspectorWindow()
        }
        .windowStyle(.plain)
        .windowResizability(.contentSize)
        .defaultPosition(.topTrailing)

        // Menu bar extra
        MenuBarExtra("Status", systemImage: "circle.fill") {
            StatusMenu()
        }
        .menuBarExtraStyle(.window)
    }
}

Window Styles

Style Use Case
.automatic Standard app windows
.hiddenTitleBar Content-focused (media players)
.plain Utility windows, panels
.unified Integrated toolbar appearance
.unifiedCompact Compact toolbar height

Window State Restoration

WindowGroup {
    ContentView()
}
.handlesExternalEvents(matching: Set(arrayLiteral: "main"))
.commands {
    CommandGroup(replacing: .newItem) {
        Button("New Document") {
            // Handle new document
        }
        .keyboardShortcut("n")
    }
}

Document-Based Apps

@main
struct DocumentApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: MyDocument()) { file in
            DocumentView(document: file.$document)
        }
        .commands {
            CommandGroup(after: .saveItem) {
                Button("Export...") { }
                    .keyboardShortcut("e", modifiers: [.command, .shift])
            }
        }
    }
}

struct MyDocument: FileDocument {
    static var readableContentTypes: [UTType] { [.plainText] }

    init(configuration: ReadConfiguration) throws { }
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { }
}

4. Toolbar & Menu Bar

Toolbar Configuration

.toolbar {
    // Leading items (macOS places these before title)
    ToolbarItem(placement: .navigation) {
        Button(action: goBack) {
            Label("Back", systemImage: "chevron.left")
        }
    }

    // Principal (centered)
    ToolbarItem(placement: .principal) {
        Picker("View Mode", selection: $viewMode) {
            Label("Icons", systemImage: "square.grid.2x2").tag(ViewMode.icons)
            Label("List", systemImage: "list.bullet").tag(ViewMode.list)
        }
        .pickerStyle(.segmented)
    }

    // Trailing items
    ToolbarItemGroup(placement: .primaryAction) {
        Button(action: share) {
            Label("Share", systemImage: "square.and.arrow.up")
        }

        Button(action: toggleInspector) {
            Label("Inspector", systemImage: "sidebar.trailing")
        }
    }
}
.toolbarRole(.editor) // or .browser, .automatic

Custom Menu Bar

.commands {
    // Replace existing menu group
    CommandGroup(replacing: .newItem) {
        Button("New Project") { }
            .keyboardShortcut("n")
        Button("New from Template...") { }
            .keyboardShortcut("n", modifiers: [.command, .shift])
    }

    // Add to existing group
    CommandGroup(after: .sidebar) {
        Button("Toggle Inspector") { }
            .keyboardShortcut("i", modifiers: [.command, .option])
    }

    // Custom menu
    CommandMenu("Canvas") {
        Button("Zoom In") { }
            .keyboardShortcut("+")
        Button("Zoom Out") { }
            .keyboardShortcut("-")
        Divider()
        Button("Fit to Window") { }
            .keyboardShortcut("0")
    }
}

Menu Bar Apps

MenuBarExtra("App Status", systemImage: statusIcon) {
    VStack(alignment: .leading, spacing: 8) {
        Text("Status: \(status)")
            .font(.headline)

        Divider()

        Button("Open Main Window") {
            openWindow(id: "main")
        }

        Button("Quit") {
            NSApplication.shared.terminate(nil)
        }
        .keyboardShortcut("q")
    }
    .padding()
}
.menuBarExtraStyle(.window) // or .menu for simple dropdown

5. Keyboard Shortcuts

Standard macOS Shortcuts

Always implement these when applicable:

Action Shortcut Implementation
New ⌘N .keyboardShortcut("n")
Open ⌘O .keyboardShortcut("o")
Save ⌘S .keyboardShortcut("s")
Close ⌘W .keyboardShortcut("w")
Undo ⌘Z .keyboardShortcut("z")
Redo ⇧⌘Z .keyboardShortcut("z", modifiers: [.command, .shift])
Cut ⌘X .keyboardShortcut("x")
Copy ⌘C .keyboardShortcut("c")
Paste ⌘V .keyboardShortcut("v")
Select All ⌘A .keyboardShortcut("a")
Find ⌘F .keyboardShortcut("f")
Preferences ⌘, .keyboardShortcut(",")
Hide ⌘H System handled
Quit ⌘Q System handled

Custom Shortcuts

Button("Toggle Sidebar") {
    toggleSidebar()
}
.keyboardShortcut("s", modifiers: [.command, .control])

// Function keys
Button("Refresh") { }
    .keyboardShortcut(KeyEquivalent.init(Character(UnicodeScalar(NSF5FunctionKey)!)))

// Arrow keys
Button("Next") { }
    .keyboardShortcut(.rightArrow)

6. Components with Liquid Glass

Control Sizing

Size Shape Use Case
Mini Rounded rect Compact toolbars, dense UIs
Small Rounded rect Secondary controls, sidebars
Regular Rounded rect Primary controls (default)
Large Capsule Prominent actions
Extra Large Capsule + Glass Hero CTAs, onboarding
// Size modifiers
Button("Action") { }
    .controlSize(.mini)     // Smallest
    .controlSize(.small)    // Compact
    .controlSize(.regular)  // Default
    .controlSize(.large)    // Prominent
    .controlSize(.extraLarge) // Hero (macOS 15+)

Buttons

// Primary action (prominent)
Button("Save Changes") { }
    .buttonStyle(.borderedProminent)
    .controlSize(.large)

// Secondary action
Button("Cancel") { }
    .buttonStyle(.bordered)

// Tertiary/link style
Button("Learn More") { }
    .buttonStyle(.plain)
    .foregroundStyle(.link)

// Destructive
Button("Delete", role: .destructive) { }
    .buttonStyle(.bordered)

// Toolbar button
Button { } label: {
    Label("Add", systemImage: "plus")
}
.buttonStyle(.borderless)

Text Fields

// Standard text field
TextField("Search", text: $query)
    .textFieldStyle(.roundedBorder)

// Search field with tokens
TextField("Search", text: $query)
    .searchable(text: $query, tokens: $tokens) { token in
        Label(token.name, systemImage: token.icon)
    }

// Secure field
SecureField("Password", text: $password)
    .textFieldStyle(.roundedBorder)

Tables

Table(items, selection: $selection) {
    TableColumn("Name", value: \.name)
        .width(min: 100, ideal: 150)

    TableColumn("Date") { item in
        Text(item.date, format: .dateTime)
    }
    .width(100)

    TableColumn("Status") { item in
        StatusBadge(status: item.status)
    }
    .width(80)
}
.tableStyle(.inset(alternatesRowBackgrounds: true))
.contextMenu(forSelectionType: Item.ID.self) { selection in
    Button("Open") { }
    Button("Delete", role: .destructive) { }
}

Popovers and Sheets

// Popover
Button("Info") {
    showPopover = true
}
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
    InfoView()
        .frame(width: 300, height: 200)
        .padding()
}

// Sheet
.sheet(isPresented: $showSheet) {
    SheetContent()
        .frame(minWidth: 400, minHeight: 300)
}

// Alert
.alert("Delete Item?", isPresented: $showAlert) {
    Button("Cancel", role: .cancel) { }
    Button("Delete", role: .destructive) {
        deleteItem()
    }
} message: {
    Text("This action cannot be undone.")
}

7. Typography & Colors

System Typography

// Semantic styles (preferred)
Text("Title").font(.largeTitle)      // 26pt bold
Text("Headline").font(.headline)      // 13pt semibold
Text("Subheadline").font(.subheadline) // 11pt regular
Text("Body").font(.body)              // 13pt regular
Text("Callout").font(.callout)        // 12pt regular
Text("Caption").font(.caption)        // 10pt regular
Text("Caption 2").font(.caption2)     // 10pt regular

// Monospaced for code
Text("let x = 1").font(.system(.body, design: .monospaced))

Semantic Colors

// Foreground
.foregroundStyle(.primary)            // Primary text
.foregroundStyle(.secondary)          // Secondary text
.foregroundStyle(.tertiary)           // Tertiary text
.foregroundStyle(.quaternary)         // Quaternary text

// Backgrounds
.background(.background)              // Window background
.background(.regularMaterial)         // Translucent material

// Accent colors
.tint(.accentColor)                   // App accent color
.foregroundStyle(.link)               // Clickable links

// Semantic colors
Color.red                             // System red (adapts to light/dark)
Color.blue                            // System blue

Vibrancy and Materials

// Materials (adapt to background content)
.background(.ultraThinMaterial)       // Most transparent
.background(.thinMaterial)
.background(.regularMaterial)         // Default
.background(.thickMaterial)
.background(.ultraThickMaterial)      // Least transparent

// Vibrancy in sidebars
List { }
    .listStyle(.sidebar)
    .scrollContentBackground(.hidden)
    .background(.ultraThinMaterial)

8. Spacing & Layout

8-Point Grid System

// Standard spacing values
VStack(spacing: 8) { }                // Standard
VStack(spacing: 16) { }               // Section spacing
VStack(spacing: 20) { }               // Group spacing

// Padding
.padding(8)                           // Tight
.padding(12)                          // Standard
.padding(16)                          // Comfortable
.padding(20)                          // Spacious

// Content margins
.contentMargins(16)                   // Uniform margins
.contentMargins(.horizontal, 20)      // Horizontal only

Safe Areas

// Respect toolbar safe area
.safeAreaInset(edge: .top) {
    ToolbarContent()
}

// Ignore safe area for backgrounds
.ignoresSafeArea(.container, edges: .top)

// Content that should avoid toolbar
.safeAreaPadding(.top)

Minimum Touch/Click Targets

// Minimum 44x44 points for clickable elements
Button { } label: {
    Image(systemName: "gear")
}
.frame(minWidth: 44, minHeight: 44)

// Use contentShape for larger hit areas
RoundedRectangle(cornerRadius: 8)
    .frame(width: 200, height: 100)
    .contentShape(Rectangle())
    .onTapGesture { }

Adaptive Layouts

// Responsive to window size
GeometryReader { geometry in
    if geometry.size.width > 600 {
        HStack { content }
    } else {
        VStack { content }
    }
}

// Grid that adapts
LazyVGrid(columns: [
    GridItem(.adaptive(minimum: 150, maximum: 250))
], spacing: 16) {
    ForEach(items) { ItemView(item: $0) }
}

9. Accessibility

VoiceOver

// Labels and hints
Button { } label: {
    Image(systemName: "plus")
}
.accessibilityLabel("Add item")
.accessibilityHint("Creates a new item in your library")

// Grouping related elements
VStack {
    Text(item.title)
    Text(item.subtitle)
}
.accessibilityElement(children: .combine)

// Custom actions
.accessibilityAction(named: "Delete") {
    deleteItem()
}

Keyboard Navigation

// Focus management
@FocusState private var focusedField: Field?

TextField("Name", text: $name)
    .focused($focusedField, equals: .name)
    .onSubmit {
        focusedField = .email
    }

// Focusable custom views
.focusable()
.onMoveCommand { direction in
    handleArrowKey(direction)
}

Dynamic Type

// Scales with user preference
Text("Content")
    .dynamicTypeSize(.large ... .accessibility3)

// Fixed size when necessary (use sparingly)
Text("Fixed")
    .dynamicTypeSize(.large)

Reduce Motion

@Environment(\.accessibilityReduceMotion) var reduceMotion

.animation(reduceMotion ? .none : .spring(), value: isExpanded)

// Alternative non-animated transitions
.transaction { transaction in
    if reduceMotion {
        transaction.animation = nil
    }
}

High Contrast

@Environment(\.colorSchemeContrast) var contrast

// Increase contrast when needed
.foregroundStyle(contrast == .increased ? .primary : .secondary)

10. Modern API Replacements

Deprecated → Modern

Deprecated Modern Notes
NavigationView NavigationSplitView / NavigationStack Split for macOS, Stack for simple flows
.navigationViewStyle(.columns) NavigationSplitView Built-in column support
List { }.listStyle(.sidebar) with NavigationLink NavigationSplitView sidebar Proper split view behavior
.toolbar { ToolbarItem(...) } in detail .toolbar on NavigationSplitView Toolbar applies to correct scope
NSWindowController WindowGroup / Window Pure SwiftUI window management
NSMenu / NSMenuItem .commands { } / CommandMenu Declarative menus
NSToolbar .toolbar { } SwiftUI toolbar API
NSTouchBar .touchBar { } SwiftUI Touch Bar
NSOpenPanel.begin() .fileImporter() SwiftUI file dialog
NSSavePanel.begin() .fileExporter() SwiftUI save dialog
.background(Color.clear) for materials .background(.regularMaterial) Proper material support
Custom blur effects .glassEffect() (Tahoe) Native Liquid Glass

AppKit Interop (When Needed)

// Wrap AppKit view
struct NSViewWrapper: NSViewRepresentable {
    func makeNSView(context: Context) -> NSView {
        // Create and configure NSView
    }

    func updateNSView(_ nsView: NSView, context: Context) {
        // Update when SwiftUI state changes
    }
}

// Access NSWindow
.background(WindowAccessor { window in
    window?.titlebarAppearsTransparent = true
})

11. Review Checklist

Liquid Glass & Materials

  • Sidebars use .glassEffect() or appropriate material
  • Toolbars have translucent appearance
  • Content areas remain clear (not overly translucent)
  • Materials adapt properly to light/dark mode
  • Fallback materials provided for pre-Tahoe

Navigation & Windows

  • NavigationSplitView used for multi-column layouts
  • Sidebar has proper min/max width constraints
  • Inspector panel available for detail/properties
  • Window restoration configured
  • Document-based apps use DocumentGroup
  • Multiple window sizes tested

Controls & Interaction

  • Control sizes appropriate for context
  • Primary actions use .borderedProminent
  • Destructive actions properly marked with .destructive role
  • Tables have context menus
  • Popovers have appropriate sizing

Keyboard & Shortcuts

  • Standard shortcuts implemented (⌘N, ⌘S, ⌘W, etc.)
  • Custom shortcuts don’t conflict with system
  • All interactive elements keyboard accessible
  • Focus order logical
  • Menu bar commands have shortcuts

Accessibility

  • All images have accessibility labels
  • Custom controls have proper roles
  • VoiceOver tested
  • Keyboard navigation complete
  • Reduce Motion respected
  • High Contrast mode tested

Platform Conventions

  • App uses system appearance (not custom chrome)
  • Settings in Preferences window (not separate)
  • File dialogs use system sheets
  • Drag and drop supported where expected
  • Services menu integration (if applicable)

Visual Polish

  • 8-point grid alignment
  • Consistent spacing throughout
  • Semantic colors used (adapts to themes)
  • Typography follows SF Pro guidelines
  • Minimum 44pt touch targets

Quick Reference: Control Shapes by Size

┌─────────────────────────────────────────────────────┐
│  Mini/Small/Medium      │    Large/XLarge           │
│  ┌──────────────────┐   │    ╭──────────────────╮   │
│  │   Rounded Rect   │   │    │     Capsule      │   │
│  └──────────────────┘   │    ╰──────────────────╯   │
│                         │                           │
│  Compact layouts        │    Hero actions           │
│  Toolbars, sidebars     │    Onboarding, CTAs       │
└─────────────────────────────────────────────────────┘

Quick Reference: Window Styles

┌─────────────────────────────────────────────────────┐
│  .automatic        Standard window with titlebar    │
│  .hiddenTitleBar   Full content, titlebar hidden    │
│  .plain            No chrome, utility panels        │
│  .unified          Toolbar merges with titlebar     │
│  .unifiedCompact   Compact unified toolbar          │
└─────────────────────────────────────────────────────┘

Quick Reference: Navigation Patterns

┌─────────────┬───────────────┬─────────────────────┐
│  Sidebar    │   Content     │      Detail         │
│             │               │                     │
│  Source     │   List or     │   Selected item     │
│  List       │   Grid        │   properties        │
│             │               │                     │
│  Collections│   Items       │   Inspector panel   │
│  Folders    │   Browse      │   Edit view         │
│             │               │                     │
│  Min: 180   │   Flexible    │   Min: 300          │
│  Max: 300   │               │   Ideal: 400+       │
└─────────────┴───────────────┴─────────────────────┘
       NavigationSplitView (three-column)