Apple Human Interface Guidelines for iPhone. Use when building, reviewing, or refactoring SwiftUI/UIKit interfaces for iOS. Triggers on tasks involving iPhone UI, iOS components, accessibility, Dynamic Type, Dark Mode, or HIG compliance.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Apple Human Interface Guidelines for iPhone. Use when building, reviewing, or refactoring SwiftUI/UIKit interfaces for iOS. Triggers on tasks involving iPhone UI, iOS components, accessibility, Dynamic Type, Dark Mode, or HIG compliance.
iOS Design Guidelines for iPhone
Comprehensive rules derived from Apple's Human Interface Guidelines. Apply these when building, reviewing, or refactoring any iPhone app interface.
1. Layout & Safe Areas
Impact: CRITICAL
Rule 1.1: Minimum 44pt Touch Targets
All interactive elements must have a minimum tap target of 44x44 points. This includes buttons, links, toggles, and custom controls.
// 20pt icon with no padding — too small to tap reliablyButton(action: save) {
Image(systemName: "checkmark")
.font(.system(size: 20))
}
// Missing .frame(minWidth: 44, minHeight: 44)
Rule 1.2: Respect Safe Areas
Never place interactive or essential content under the status bar, Dynamic Island, or home indicator. Use SwiftUI's automatic safe area handling or UIKit's safeAreaLayoutGuide.
Correct:
structContentView: View {
body: {
{
()
}
}
}
var
some
View
VStack
Text
"Content"
// SwiftUI respects safe areas by default
Incorrect:
structContentView: View {
var body: someView {
VStack {
Text("Content")
}
.ignoresSafeArea() // Content will be clipped under notch/Dynamic Island
}
}
Use .ignoresSafeArea() only for background fills, images, or decorative elements — never for text or interactive controls.
Rule 1.3: Primary Actions in the Thumb Zone
Place primary actions at the bottom of the screen where the user's thumb naturally rests. Secondary actions and navigation belong at the top.
// Hamburger menu hidden behind three lines — discoverability is near zeroNavigationView {
Button(action: { showMenu.toggle() }) {
Image(systemName: "line.horizontal.3")
}
}
Rule 2.2: Never Use Hamburger Menus
Hamburger (drawer) menus hide navigation, reduce discoverability, and violate iOS conventions. Use a tab bar instead. If you have more than 5 sections, consolidate or use a "More" tab.
Rule 2.3: Large Titles in Primary Views
Use .navigationBarTitleDisplayMode(.large) for top-level views. Titles transition to inline (.inline) when the user scrolls.
When users navigate back and then forward, or switch tabs, restore the previous scroll position and input state. Use @SceneStorage or @State to persist view state.
Rule 2.7: Prefer Recognition Over Recall
Keep current location, recent choices, and available destinations visible. Restore tab, scroll, filter, and selection state so users continue from recognition instead of reconstructing context from memory.
3. Typography & Dynamic Type
Impact: HIGH
Rule 3.1: Use Built-in Text Styles
Always use semantic text styles rather than hardcoded sizes. These scale automatically with Dynamic Type.
VStack(alignment: .leading, spacing: 4) {
Text("Section Title")
.font(.system(size: 17, weight: .semibold)) // Won't scale with Dynamic TypeText("Body content")
.font(.system(size: 15)) // Won't scale with Dynamic Type
}
Rule 3.2: Support Dynamic Type Including Accessibility Sizes
Dynamic Type can scale text up to approximately 200% at the largest accessibility sizes. Layouts must reflow — never truncate or clip essential text.
Correct:
HStack {
Image(systemName: "star")
Text("Favorites")
.font(.body)
}
// At accessibility sizes, consider using ViewThatFits or// AnyLayout to switch from HStack to VStack
Use @Environment(\.dynamicTypeSize) to detect size category and adapt layouts:
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: someView {
if dynamicTypeSize.isAccessibilitySize {
VStack { content }
} else {
HStack { content }
}
}
Rule 3.3: Custom Fonts Must Scale with Dynamic Type
If you use a custom typeface, scale it so it responds to Dynamic Type. The API differs by framework.
let metrics =UIFontMetrics(forTextStyle: .body)
let customFont =UIFont(name: "CustomFont-Regular", size: 17)!
label.font = metrics.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory =true
Rule 3.4: SF Pro as System Font
Use the system font (SF Pro) unless brand requirements dictate otherwise. SF Pro is optimized for legibility on Apple displays.
Rule 3.5: Minimum 11pt Text
Never display text smaller than 11pt. Prefer 17pt for body text. Use the caption2 style (11pt) as the absolute minimum.
Rule 3.6: Hierarchy Through Weight and Size
Establish visual hierarchy through font weight and size. Do not rely solely on color to differentiate text levels.
4. Color & Dark Mode
Impact: HIGH
Rule 4.1: Use Semantic System Colors
Use system-provided semantic colors that automatically adapt to light and dark modes.
Correct:
Text("Primary text")
.foregroundStyle(.primary) // Adapts to light/darkText("Secondary info")
.foregroundStyle(.secondary)
VStack { }
.background(Color(.systemBackground)) // White in light, black in dark
Incorrect:
Text("Primary text")
.foregroundColor(.black) // Invisible on dark backgroundsVStack { }
.background(.white) // Blinding in Dark Mode
Rule 4.2: Provide Light and Dark Variants for Custom Colors
Define custom colors in the asset catalog with both Any Appearance and Dark Appearance variants.
// In Assets.xcassets, define "BrandBlue" with:// Any Appearance: #0066CC// Dark Appearance: #4DA3FFText("Brand text")
.foregroundStyle(Color("BrandBlue")) // Automatically switches
Rule 4.3: Never Rely on Color Alone
Always pair color with text, icons, or shapes to convey meaning. Approximately 8% of men have some form of color vision deficiency.
Ensure VoiceOver reads elements in a logical order. Use .accessibilitySortPriority() to adjust when the visual layout doesn't match the reading order.
VStack {
Text("Price: $29.99")
.accessibilitySortPriority(1) // Read second (lower number = lower priority)Text("Product Name")
.accessibilitySortPriority(2) // Read first (higher number = higher priority)
}
Rule 5.3: Support Bold Text
When the user enables Bold Text in Settings, custom-rendered text must adapt. SwiftUI text styles handle this automatically. For SwiftUI custom rendering, use @Environment(\.legibilityWeight) to apply heavier weights. UIKit code must check UIAccessibility.isBoldTextEnabled and re-query on UIAccessibility.boldTextStatusDidChangeNotification.
Correct:
// SwiftUI — standard text styles adapt automaticallyText("Section Header")
.font(.headline)
// SwiftUI — custom rendering respects legibilityWeight@Environment(\.legibilityWeight) var legibilityWeight
var body: someView {
Text("Custom Label")
.fontWeight(legibilityWeight == .bold ? .bold : .regular)
}
Incorrect:
// Hardcoded weight ignores Bold Text preference
label.font =UIFont.systemFont(ofSize: 17, weight: .regular)
// Missing: re-query font when UIAccessibility.boldTextStatusDidChangeNotification fires
Rule 5.4: Support Reduce Motion
Disable decorative animations and parallax when Reduce Motion is enabled. Use @Environment(\.accessibilityReduceMotion).
Correct:
@Environment(\.accessibilityReduceMotion) var reduceMotion
var body: someView {
CardView()
.animation(reduceMotion ? nil : .spring(), value: isExpanded)
}
Rule 5.5: Support Increase Contrast
When the user enables Increase Contrast, ensure custom colors have higher-contrast variants. Use @Environment(\.colorSchemeContrast) to detect.
Rule 5.6: Don't Convey Info Only by Color, Shape, or Position
Information must be available through multiple channels. Pair visual indicators with text or accessibility descriptions.
Rule 5.7: Alternative Interactions for All Gestures
Every custom gesture must have an equivalent tap-based or menu-based alternative for users who cannot perform complex gestures.
Rule 5.8: Support Switch Control and Full Keyboard Access
Ensure all interactions work with Switch Control (external switches) and Full Keyboard Access (Bluetooth keyboards). Test navigation order and focus behavior.
6. Gestures & Input
Impact: HIGH
Rule 6.1: Use Standard Gestures
Use the standard iOS gesture vocabulary: tap, long press, swipe, pinch, rotate. Users already understand these.
Gesture
Standard Use
Tap
Primary action, selection
Long press
Context menu, preview
Swipe horizontal
Delete, archive, navigate back
Swipe vertical
Scroll, dismiss sheet
Pinch
Zoom in/out
Two-finger rotate
Rotate content
Rule 6.2: Never Override System Gestures
These gestures are reserved by the system and must not be intercepted:
Swipe from left edge (back navigation)
Swipe down from top-left (Notification Center)
Swipe down from top-right (Control Center)
Swipe up from bottom (home / app switcher)
Rule 6.3: Custom Gestures Must Be Discoverable
If you add a custom gesture, provide visual hints (e.g., a grabber handle) and ensure the action is also available through a visible button or menu item.
Rule 6.4: Support All Input Methods
Design for touch first, but also support:
Hardware keyboards (iPad keyboard accessories, Bluetooth keyboards)
Use alerts sparingly for critical information that requires a decision. Prefer 2 buttons; maximum 3. The destructive option should use .destructive role.
// Alert for non-critical info — should be a banner or toast
.alert("Tip", isPresented: $showTip) {
Button("OK") { }
} message: {
Text("Swipe left to delete items.")
}
Rule 7.3: Sheets for Scoped Tasks
Present sheets for self-contained tasks. Always provide a way to dismiss (close button or swipe down). Use .presentationDetents() for half-height sheets.
Determinate (ProgressView(value:total:)) for operations with known duration
Indeterminate (ProgressView()) for unknown duration
Never block the entire screen with a spinner
Rule 7.9: SF Symbols — Rendering Modes
Use the appropriate rendering mode for each symbol. Monochrome is the default; hierarchical, palette, and multicolor provide richer expression where appropriate. Always prefer the symbol rendering mode that best communicates meaning — do not default to monochrome when multicolor conveys critical state.
Correct:
// Hierarchical: single color with automatic opacity layersImage(systemName: "person.crop.circle.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
// Multicolor: system-defined color per layer (e.g., battery, weather)Image(systemName: "battery.100percent.bolt")
.symbolRenderingMode(.multicolor)
// Palette: explicit per-layer colorsImage(systemName: "folder.badge.plus")
.symbolRenderingMode(.palette)
.foregroundStyle(.white, .blue)
Incorrect:
// Monochrome on a symbol that has meaningful multicolor layersImage(systemName: "battery.100percent.bolt")
.foregroundColor(.gray) // loses the contextual color meaning
Rule 7.10: SF Symbols — Weight and Scale
Match the symbol weight to adjacent text weight. Use scale variants (.small, .medium, .large) rather than resizing. The symbol weight should never appear heavier than adjacent text.
Correct:
Label("Download", systemImage: "arrow.down.circle.fill")
.font(.body.weight(.semibold))
// Symbol inherits .semibold weight automatically via Label
Use symbolEffect for symbol state transitions. Prefer discrete effects (.bounce, .pulse) for actions and indefinite effects (.variableColor) for ongoing state. Do not use manual cross-fade between symbol names when contentTransition(.symbolEffect) is available.
if isLoading {
ProgressView("Loading...") // Blocks the entire view
} else {
List(items) { item inItemRow(item: item) }
}
Rule 8.3: Launch Screen — Match First Screen
The launch storyboard must visually match the initial screen of the app. No splash logos, no branding screens. This creates the perception of instant launch.
Rule 8.4: Modality — Use Sparingly
Present modal views only when the user must complete or abandon a focused task. Always provide a clear dismiss action. Never stack modals on top of modals.
Rule 8.5: Notifications — High Value Only
Only send notifications for content the user genuinely cares about. Support actionable notifications. Categorize notifications so users can control them granularly.
Rule 8.6: Settings Placement
Frequent settings: In-app settings screen accessible from a profile or gear icon
Privacy/permission settings: Defer to the system Settings app via URL scheme
Never duplicate system-level controls in-app
Rule 8.7: Feedback — Visual + Haptic
Provide immediate feedback for every user action:
Visual state change (button highlight, animation)
Haptic feedback for significant actions using UIImpactFeedbackGenerator, UINotificationFeedbackGenerator, or UISelectionFeedbackGenerator
Button("Complete") {
let generator =UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
completeTask()
}
Rule 8.8: Show Waiting States Immediately
If an action cannot complete immediately, acknowledge the tap at once, then show inline progress, skeletons, or partial results. Never leave the interface visually unchanged while work continues.
9. Privacy & Permissions
Impact: HIGH
Rule 9.1: Request Permissions in Context
Request a permission at the moment the user takes an action that needs it — never at app launch.
Correct:
Button("Take Photo") {
// Request camera permission only when the user taps this buttonAVCaptureDevice.requestAccess(for: .video) { granted inif granted { showCamera =true }
}
}
Incorrect:
// In AppDelegate.didFinishLaunching — too early, no contextfuncapplication(_application: UIApplication, didFinishLaunchingWithOptions ...) {
AVCaptureDevice.requestAccess(for: .video) { _in }
CLLocationManager().requestWhenInUseAuthorization()
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { _, _in }
}
Rule 9.2: Explain Before System Prompt
Show a custom explanation screen before triggering the system permission dialog. The system dialog only appears once — if the user denies, the app must direct them to Settings.
structLocationExplanation: View {
var body: someView {
VStack(spacing: 16) {
Image(systemName: "location.fill")
.font(.largeTitle)
Text("Find Nearby Stores")
.font(.headline)
Text("We use your location to show stores within walking distance. Your location is never shared or stored.")
.font(.body)
.multilineTextAlignment(.center)
Button("Enable Location") {
locationManager.requestWhenInUseAuthorization()
}
.buttonStyle(.borderedProminent)
Button("Not Now") { dismiss() }
.foregroundStyle(.secondary)
}
.padding()
}
}
Rule 9.3: Support Sign in with Apple
If the app offers any third-party sign-in (Google, Facebook), it must also offer Sign in with Apple. Present it as the first option.
Rule 9.4: Don't Require Accounts Unless Necessary
Let users explore the app before requiring sign-in. Gate only features that genuinely need authentication (purchases, sync, social features).
Rule 9.5: App Tracking Transparency
If you track users across apps or websites, display the ATT prompt. Respect denial — do not degrade the experience for users who opt out.
Rule 9.6: Location Button for One-Time Access
Use LocationButton for actions that need location once without requesting ongoing permission.
Provide widgets using WidgetKit for information users check frequently. Show the most useful snapshot. Since iOS 17, widgets support interactive controls: use Button and Toggle backed by App Intents for actions users perform directly from the widget without opening the app.
These are common mistakes that violate the iOS Human Interface Guidelines. Never do these:
Hamburger menus — Use a tab bar. Hamburger menus hide navigation and reduce feature discoverability by up to 50%.
Custom back buttons that break swipe-back — If you replace the back button, ensure the swipe-from-left-edge gesture still works via NavigationStack.
Full-screen blocking spinners — Use skeleton views or inline progress indicators. Blocking spinners make the app feel frozen.
Splash screens with logos — The launch screen must mirror the first screen of the app. Branding delays feel artificial.
Requesting all permissions at launch — Asking for camera, location, notifications, and contacts on first launch guarantees most will be denied.
Hardcoded font sizes — Use text styles. Hardcoded sizes ignore Dynamic Type and accessibility preferences, breaking the app for millions of users.
Using only color to indicate state — Red/green for valid/invalid excludes colorblind users. Always pair with icons or text.
Alerts for non-critical information — Alerts interrupt flow and require dismissal. Use banners, toasts, or inline messages for tips and non-critical information.
Hiding the tab bar on push — Tab bars should remain visible throughout navigation within a tab. Hiding them disorients users.
Ignoring safe areas — Using .ignoresSafeArea() on content views causes text and buttons to disappear under the notch, Dynamic Island, or home indicator.
Non-dismissable modals — Every modal must have a clear dismiss path (close button, cancel, swipe down). Trapping users in a modal is hostile.
Custom gestures without alternatives — A three-finger swipe for undo is unusable for many people. Provide a visible button or menu item as well.
Tiny touch targets — Buttons and links smaller than 44pt cause mis-taps, especially in lists and toolbars.
Stacked modals — Presenting a sheet on top of a sheet on top of a sheet creates navigation confusion. Use navigation within a single modal instead.
Dark Mode as an afterthought — Using hardcoded colors means the app is either broken in Dark Mode or light mode. Always use semantic colors.
Credits & Attribution
This skill is based on the excellent work by
ehmo.
Special thanks to ehmo for their generous open-source contributions, which helped shape this skill collection.
Adapted by webconsulting.at for this skill collection