mcpField
M
macos-hig-designer

macos-hig-designer

github.com/designnotdrumデザイン・UI1122 回インストール39 週間前に更新
リポジトリ

macos-hig-designer について

AppleのHuman Interface Guidelinesに沿ったネイティブmacOSアプリの設計とレビューを支援するスキルです。macOS TahoeのLiquid Glass、SwiftUIの実装パターン、API更新、アクセシビリティ、UIチェックリストを扱います。

主な機能

  1. 01Liquid Glassのデザイン指針
  2. 02SwiftUIのナビゲーションとウィンドウ設計
  3. 03ツールバー、メニュー、ショートカットのパターン
  4. 04アクセシビリティ実装の指針
  5. 05非推奨APIの置き換え案
  6. 06HIGレビュー用チェックリスト

活用シーン

  1. 01macOS UIコードのHIG準拠レビュー
  2. 02古いSwiftUIやAppKitの実装パターンの更新
  3. 03アクセシブルなアプリのナビゲーションとウィンドウ設計

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/designnotdrum/skills --skill macos-hig-designer

インストール後、Claude Code で/macos-hig-designerを入力して読み込まれたか確認します。

詳細

macOS HIG デザイナー

macOS Tahoe の Liquid Glass デザインシステムを使用して、Apple のヒューマンインターフェイスガイドラインに準拠したネイティブ macOS アプリケーションを設計します。

ワークフローの判断ツリー

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. デザイン原則(macOS Tahoe)

3 つの基本原則
原則説明実装
階層Liquid Glass の半透明性による視覚的なレイヤー.glassEffect()、マテリアル、奥行きを使用します
調和ハードウェアとソフトウェア間の同心円状の整列角を丸くし、一貫した半径と流れるような形状を採用します
一貫性コンテキストに応じて適応するプラットフォームの慣例標準的なパターンに従い、ユーザー設定を尊重します
Liquid Glass の理念

Liquid Glass は、透明性、反射、屈折、流動性を、すりガラスのような美しさと組み合わせます。

// 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)

Liquid Glass を使用する場面:

  • サイドバー、ツールバー、ナビゲーション用のクローム
  • フローティングパネルやポップオーバー
  • Dock やウィジェットの背景
  • システムレベルの UI 要素

使用しない場面:

  • 主要なコンテンツ領域(書類、メディア)
  • データ量の多い表示(テーブル、多数の項目があるリスト)
  • 読みやすさが重要な、テキスト中心のインターフェイス

2. ナビゲーションパターン

NavigationSplitView(主要パターン)

書類ベースやコンテンツ中心のアプリ向けの 3 カラムレイアウト:

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)
        }
    }
}
サイドバーパターン
// 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)
インスペクターパネル(末尾側のサイドバー)
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. ウィンドウ管理

ウィンドウの設定
@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)
    }
}
ウィンドウスタイル
スタイル使用例
.automatic標準的なアプリウィンドウ
.hiddenTitleBarコンテンツ重視(メディアプレーヤー)
.plainユーティリティウィンドウ、パネル
.unifiedツールバーと統合された外観
.unifiedCompactコンパクトなツールバーの高さ
ウィンドウ状態の復元
WindowGroup {
    ContentView()
}
.handlesExternalEvents(matching: Set(arrayLiteral: "main"))
.commands {
    CommandGroup(replacing: .newItem) {
        Button("New Document") {
            // Handle new document
        }
        .keyboardShortcut("n")
    }
}
書類ベースのアプリ
@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 {
    // 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
カスタムメニューバー
.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")
    }
}
メニューバーアプリ
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. キーボードショートカット

macOS の標準ショートカット

該当する場合は、必ず以下を実装します。

アクションショートカット実装
新規⌘N.keyboardShortcut("n")
開く⌘O.keyboardShortcut("o")
保存⌘S.keyboardShortcut("s")
閉じる⌘W.keyboardShortcut("w")
取り消す⌘Z.keyboardShortcut("z")
やり直す⇧⌘Z.keyboardShortcut("z", modifiers: [.command, .shift])
カット⌘X.keyboardShortcut("x")
コピー⌘C.keyboardShortcut("c")
ペースト⌘V.keyboardShortcut("v")
すべてを選択⌘A.keyboardShortcut("a")
検索⌘F.keyboardShortcut("f")
設定⌘,.keyboardShortcut(",")
隠す⌘Hシステムが処理します
終了⌘Qシステムが処理します
カスタムショートカット
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. Liquid Glass を使用したコンポーネント

コントロールのサイズ
サイズ形状使用例
ミニ角丸長方形コンパクトなツールバー、密度の高い UI
小角丸長方形補助的なコントロール、サイドバー
標準角丸長方形主要なコントロール(デフォルト)
大カプセル目立たせるアクション
特大カプセル+Glass主役となる CTA、オンボーディング
// Size modifiers
Button("Action") { }
    .controlSize(.mini)     // Smallest
    .controlSize(.small)    // Compact
    .controlSize(.regular)  // Default
    .controlSize(.large)    // Prominent
    .controlSize(.extraLarge) // Hero (macOS 15+)
ボタン
// 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)
テキストフィールド
// 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)
テーブル
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) { }
}
ポップオーバーとシート
// 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. タイポグラフィと色

システムタイポグラフィ
// 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))
セマンティックカラー
// 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
ビビッドネスとマテリアル
// 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. 間隔とレイアウト

8 ポイントグリッドシステム
// 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
セーフエリア
// 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 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 { }
アダプティブレイアウト
// 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. アクセシビリティ

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()
}
キーボードナビゲーション
// 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)
}
ダイナミックタイプ
// Scales with user preference
Text("Content")
    .dynamicTypeSize(.large ... .accessibility3)

// Fixed size when necessary (use sparingly)
Text("Fixed")
    .dynamicTypeSize(.large)
視差効果を減らす
@Environment(\.accessibilityReduceMotion) var reduceMotion

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

// Alternative non-animated transitions
.transaction { transaction in
    if reduceMotion {
        transaction.animation = nil
    }
}
高コントラスト
@Environment(\.colorSchemeContrast) var contrast

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

10. 最新 API への置き換え

非推奨 → モダン
非推奨モダン備考
NavigationViewNavigationSplitView / NavigationStackmacOS では Split、シンプルなフローでは Stack を使用
.navigationViewStyle(.columns)NavigationSplitView列のサポートを標準で提供
NavigationLink を使った List { }.listStyle(.sidebar)NavigationSplitView のサイドバー適切な分割ビューの動作
詳細画面内の .toolbar { ToolbarItem(...) }NavigationSplitView の .toolbarツールバーが適切なスコープに適用されます
NSWindowControllerWindowGroup / WindowSwiftUI のみでウィンドウを管理
NSMenu / NSMenuItem.commands { } / CommandMenu宣言型メニュー
NSToolbar.toolbar { }SwiftUI のツールバー API
NSTouchBar.touchBar { }SwiftUI の Touch Bar
NSOpenPanel.begin().fileImporter()SwiftUI のファイルダイアログ
NSSavePanel.begin().fileExporter()SwiftUI の保存ダイアログ
マテリアルに .background(Color.clear) を使用.background(.regularMaterial)マテリアルを適切にサポート
カスタムのぼかし効果.glassEffect() (Tahoe)ネイティブの Liquid Glass
AppKit との相互運用(必要な場合)
// 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. レビューチェックリスト

Liquid Glass とマテリアル
  • サイドバーに .glassEffect() または適切なマテリアルを使用している
  • ツールバーが半透明になっている
  • コンテンツ領域は透明感を保っている(過度に半透明になっていない)
  • マテリアルがライト/ダークモードに適切に対応している
  • Tahoe より前のバージョン向けに代替マテリアルを用意している
ナビゲーションとウィンドウ
  • 複数列のレイアウトに NavigationSplitView を使用している
  • サイドバーに適切な最小幅/最大幅の制約がある
  • 詳細/プロパティ用のインスペクターパネルが利用できる
  • ウィンドウの復元が設定されている
  • ドキュメントベースのアプリで DocumentGroup を使用している
  • 複数のウィンドウサイズでテストしている
コントロールとインタラクション
  • コンテキストに適したサイズのコントロールを使用している
  • 主なアクションに .borderedProminent を使用している
  • 破壊的なアクションに .destructive ロールを適切に指定している
  • テーブルにコンテキストメニューがある
  • ポップオーバーのサイズが適切である
キーボードとショートカット
  • 標準的なショートカット(⌘N、⌘S、⌘W など)を実装している
  • カスタムショートカットがシステムと競合しない
  • すべてのインタラクティブ要素をキーボードで操作できる
  • フォーカスの順序が論理的である
  • メニューバーのコマンドにショートカットがある
アクセシビリティ
  • すべての画像にアクセシビリティラベルがある
  • カスタムコントロールに適切なロールがある
  • VoiceOver でテストしている
  • キーボードナビゲーションが完全である
  • 視差効果を減らす設定に対応している
  • ハイコントラストモードでテストしている
プラットフォームの慣例
  • アプリがシステムの外観を使用している(カスタムのクロームではない)
  • 設定が独立したウィンドウではなく「環境設定」ウィンドウにある
  • ファイルダイアログがシステムシートを使用している
  • 適切な場所でドラッグ&ドロップに対応している
  • 「サービス」メニューと連携している(該当する場合)
見た目の仕上げ
  • 8 ポイントグリッドに沿って配置している
  • 全体を通して間隔が統一されている
  • セマンティックカラーを使用している(テーマに応じて変化)
  • タイポグラフィが SF Pro のガイドラインに沿っている
  • タッチターゲットが最小 44pt である

クイックリファレンス:サイズ別のコントロール形状

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

クイックリファレンス:ウィンドウスタイル

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

クイックリファレンス:ナビゲーションパターン

┌─────────────┬───────────────┬─────────────────────┐
│  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)

コメント