ios-hig

Apple Human Interface Guidelines for iOS and iPadOS. Load for iOS or iPadOS design tasks.

SKILL.md

GitHub

Detection Signals

Load this skill when any of the following are present:

Signal Location
iOS target in cross-platform project React Native, Flutter, or pubspec.yaml with iOS support
Native iOS/iPadOS project .xcodeproj, .xcworkspace, Info.plist, Swift source files
SwiftUI or UIKit usage import SwiftUI, import UIKit in source files
Design task for iOS User mentions "iOS design", "HIG", "Apple guidelines", or "SwiftUI design"

Core Principle — Feel Native

iOS users expect apps to feel like a natural extension of the operating system. Deviation from platform patterns — even aesthetically — creates friction. When in doubt, prefer the native pattern over a custom one.


Navigation Patterns

Choose the navigation model that matches the information architecture. Never mix models without a clear reason.

Tab Bar

  • Use for 3–5 top-level destinations that are peers (no hierarchy between them)
  • Tabs are always visible and always accessible — never hide or disable individual tabs
  • Icons: use SF Symbols; active tab uses filled variant + tint color; inactive uses outlined variant + secondaryLabel color
  • Tab bar height: 49pt (83pt with home indicator safe area on notched devices)
  • Labels: short (1–2 words max); truncate with … only as a last resort

Navigation Stack (Push)

  • Use for hierarchical content — each level drills deeper into detail
  • Back button appears automatically; never replace it with a custom "Back" label unless the destination title is ambiguous
  • Navigation bar title: use .large style at the root level, .inline for deeper levels
  • Never push more than 3–4 levels without a shortcut back to root (e.g., long-press back button)

Modal Presentation

Style Usage
Sheet (.sheet) Lightweight tasks that don't require full context switch
Full-screen cover (.fullScreenCover) Immersive tasks (camera, onboarding, video); user cannot swipe to dismiss
Popover (iPad) Contextual information anchored to a control; auto-adapts to sheet on iPhone
  • Sheets support pull-to-dismiss by default — preserve this behavior; only disable when unsaved data is at risk (show a confirmation action sheet instead)
  • Never present a sheet from within a sheet — use navigation push instead
  • Full-screen covers must always provide an explicit dismiss control (button or gesture)

Split View (iPad)

  • Use NavigationSplitView for two- or three-column layouts on iPad
  • Sidebar (first column): persistent navigation; list of sections or categories
  • Content (second column): list or index within a section
  • Detail (third column): full detail view
  • Collapse gracefully to a single-column navigation stack on iPhone and compact-width iPad

Controls

SF Symbols

  • Always prefer SF Symbols over custom icons — they scale with Dynamic Type, support weight variants, and adapt to Dark Mode automatically
  • Match symbol weight to the surrounding text weight: regular text → regular symbol
  • Use filled variants for selected/active states; outlined for inactive
  • Symbols used as interactive controls must have an accessibility label
  • Custom symbols must follow the SF Symbols grid and optical alignment — use the SF Symbols app to verify

Buttons

Style Usage
Filled (.borderedProminent) Primary action; one per screen
Tinted (.bordered with tint) Secondary action; safe to use multiple times
Gray (.bordered) Tertiary or neutral actions
Plain (.plain) Low-emphasis, inline, or destructive actions
  • Minimum touch target: 44×44pt — use .contentShape(Rectangle()) to expand hit area without changing visual size
  • Destructive actions must use .destructive role — renders in red and triggers a confirmation if needed
  • Button labels: verb phrases ("Save Changes", "Delete Account") — never noun-only labels ("OK")

Toggles and Pickers

  • Toggle (Switch): for immediate binary settings — changes apply instantly without a "Save" button
  • Picker: use .segmented style for 2–4 mutually exclusive short options; use .menu style for longer lists
  • Stepper: for incrementing/decrementing numeric values within a known range
  • Slider: for continuous values where precision is less critical than range exploration
  • DatePicker: always use the system date picker — never build a custom one

Text Input

  • TextField: single-line; set keyboardType, textContentType, and autocorrectionDisabled appropriately
  • SecureField: for passwords — never use TextField for passwords
  • TextEditor: multi-line; add a character count label when there is a limit
  • Always dismiss the keyboard when the user taps outside the field (onTapGesture on the background)
  • Return key label should match the action: "Done", "Search", "Send", "Next"

Typography — SF Pro

Apple's system font family is SF Pro (iOS/macOS) and SF Pro Rounded (friendly contexts). Use it via Dynamic Type — never specify a fixed font size.

Dynamic Type Scales

Style Default Size Usage
largeTitle 34pt Hero page titles (used sparingly)
title1 28pt Primary screen headings
title2 22pt Secondary headings
title3 20pt Tertiary headings
headline 17pt semibold List row primaries, emphasized labels
body 17pt Primary body text
callout 16pt Secondary body, sidebars
subheadline 15pt Supporting labels
footnote 13pt Secondary descriptions, timestamps
caption1 12pt Image captions, form helper text
caption2 11pt Smallest visible labels

Rules:

  • Always use .font(.body), .font(.headline), etc. — never Font.system(size: 17)
  • Support Accessibility text sizes (up to ~310% of default) — test with "Larger Text" in Accessibility settings
  • When Accessibility sizes are enabled, consider moving labels to a vertical stack using @ScaledMetric or ViewThatFits
  • Minimum: caption2 (11pt) — never use sizes below this

Layout

Safe Area

  • Never place interactive content or critical text outside the safe area — it will be obscured by notch, Dynamic Island, or home indicator
  • Use .safeAreaInset() (SwiftUI) or safeAreaLayoutGuide (UIKit) — never hard-code pixel offsets
  • Bottom content (buttons, tab bars): account for the home indicator (34pt on notched devices)

Spacing and Margins

  • Standard content margin: 16pt from screen edges (20pt on Plus/Max models)
  • Group related content with consistent spacing; use the 8pt grid as the base unit
  • List rows: minimum 44pt height for tappable rows
  • Section headers: listRowInsets preserves the standard left inset unless deliberately removed

Grids and Collections

  • Use LazyVGrid / LazyHGrid (SwiftUI) or UICollectionViewCompositionalLayout (UIKit) for grid layouts — never manual frame placement
  • Item spacing: 8pt default; increase to 16pt for card-style items
  • Always account for variable row heights — never assume fixed item height in a collection

Dark Mode

iOS automatically switches between light and dark appearances. Designs must support both.

Color Usage

  • Always use semantic system colors — they adapt automatically: .primary, .secondary, .background, .secondaryBackground, .grouped Background, .separator, .label, .secondaryLabel
  • Custom colors must define both light and dark variants in the Asset Catalog (Appearances: Any, Dark)
  • Never hard-code Color.white for backgrounds or Color.black for text — use semantic equivalents

Images and Icons

  • SF Symbols adapt automatically — no extra work needed
  • Custom images: provide light and dark variants in the Asset Catalog when they differ
  • Avoid images with embedded text — they do not adapt to Dark Mode

Elevation and Depth

  • iOS does not use colored tonal elevation like Material — use shadows, blur (.background(.ultraThinMaterial)), or grouping to separate layers
  • Materials (ultraThinMaterial, thinMaterial, regularMaterial, thickMaterial, ultraThickMaterial) adapt to both appearances automatically

Accessibility

VoiceOver

  • Every interactive element must have an accessibilityLabel (what it is) and, when needed, an accessibilityHint (what happens when activated)
  • Decorative images: mark as .accessibilityHidden(true)
  • Custom controls: implement accessibilityValue for current state and accessibilityAdjustableAction for sliders/pickers
  • Test all key flows with VoiceOver enabled before release

Reduce Motion

  • Respect the Reduce Motion accessibility setting — replace all non-essential animations with simple crossfades or instant transitions
  • In SwiftUI: @Environment(\.accessibilityReduceMotion) var reduceMotion
  • Never use motion to convey meaning that cannot be understood without it

Increase Contrast

  • Respect the Increase Contrast setting — borders and separators become more prominent automatically with system colors; custom colors must provide high-contrast variants
  • In SwiftUI: @Environment(\.accessibilityDifferentiateWithoutColor) and @Environment(\.colorSchemeContrast)

Touch Targets

  • Minimum: 44×44pt for all tappable elements
  • For small visual elements (icons, toggles), expand the hit area with padding or .contentShape(Rectangle())

iPhone vs iPad Adaptations

The same app must adapt its layout based on the available space — not serve a different design.

Aspect iPhone (Compact) iPad (Regular)
Navigation Tab Bar + Navigation Stack Split View or Tab Bar
Modals Full-width sheets Popovers, centered sheets
Content columns Single column Two or three columns
Keyboard Full-screen, pushes content Floating or docked; content shifts
Pointer (iPadOS) N/A Support hover effects (.hoverEffect())
  • Use @Environment(\.horizontalSizeClass) to branch layout — compact = iPhone / iPad portrait in some cases; regular = iPad landscape and most iPad orientations
  • Never detect device model (UIDevice.current.userInterfaceIdiom) to branch layout — use size class; it handles Split View and Stage Manager correctly
  • Test on iPad with Stage Manager enabled — the app may appear in various window sizes

Platform Conventions to Never Break

These behaviors are deeply ingrained in iOS users. Deviating causes immediate friction:

  • Pull-to-refresh: always supported in scrollable lists that show server data
  • Swipe-to-delete: always supported in editable lists (onDelete in SwiftUI)
  • Long-press context menu: offer contextual actions on tappable items (.contextMenu)
  • Back swipe: never disable edge-swipe-to-go-back; it is a core navigation gesture
  • Scroll-to-top: tapping the status bar scrolls the active scroll view to the top — do not break this
  • Share sheet: use UIActivityViewController / ShareLink — never build a custom share UI
  • System alerts: use UIAlertController / .alert() for OS-level confirmations — never custom modal dialogs for destructive actions