macos-hig-designer
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
-
NavigationSplitViewused 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
.destructiverole - 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)