ios-accessibility
This skill provides comprehensive guidance for implementing accessibility in iOS and macOS applications across SwiftUI, UIKit, and AppKit frameworks. It covers essential practices including VoiceOver support, keyboard navigation, Dynamic Type scaling, focus management, assistive technology compatibility, and testing methodologies. Use this skill when adding or auditing accessibility features, configuring controls for screen readers, respecting system accessibility preferences, or preparing apps for App Store accessibility compliance.
git clone --depth 1 https://github.com/dpearson2699/swift-ios-skills /tmp/ios-accessibility && cp -r /tmp/ios-accessibility/skills/ios-accessibility ~/.claude/skills/ios-accessibilitySKILL.md
# iOS/macOS Accessibility - SwiftUI, UIKit, and AppKit
Build SwiftUI, UIKit, and AppKit interfaces that remain operable with VoiceOver, Switch Control, Voice Control, Full Keyboard Access, and adaptive accessibility settings.
## Contents
- [Core Principles](#core-principles)
- [How VoiceOver Reads Elements](#how-voiceover-reads-elements)
- [SwiftUI Accessibility Modifiers](#swiftui-accessibility-modifiers)
- [Focus Management](#focus-management)
- [Dynamic Type](#dynamic-type)
- [Custom Rotors](#custom-rotors)
- [System Accessibility Preferences](#system-accessibility-preferences)
- [Decorative Content](#decorative-content)
- [Voice Control](#voice-control)
- [Switch Control](#switch-control)
- [Full Keyboard Access](#full-keyboard-access)
- [Assistive Access (iOS 18+)](#assistive-access-ios-18)
- [UIKit Accessibility Patterns](#uikit-accessibility-patterns)
- [AppKit Accessibility Patterns](#appkit-accessibility-patterns)
- [Accessibility Custom Content](#accessibility-custom-content)
- [App Store Accessibility Nutrition Labels](#app-store-accessibility-nutrition-labels)
- [Testing Accessibility](#testing-accessibility)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)
---
## Core Principles
1. Prefer semantic system controls; custom controls must expose the same name,
role, value, state, and actions.
2. Preserve every task across assistive input: do not make a gesture, color,
motion, hover state, or visual arrangement the only path to meaning or action.
3. Keep focus and traversal intentional across navigation, presentation, updates,
and dismissal.
4. Let text, layout, contrast, transparency, and motion adapt to the user's
accessibility settings.
5. Treat App Store accessibility declarations as evidence-backed product claims,
not as a summary of isolated control-level support.
## How VoiceOver Reads Elements
VoiceOver reads element properties in a fixed, non-configurable order:
**Label -> Value -> Trait -> Hint**
Design your labels, values, and hints with this reading order in mind.
## SwiftUI Accessibility Modifiers
See [references/a11y-patterns.md](references/a11y-patterns.md) for detailed SwiftUI modifier examples (labels, hints, traits, grouping, custom controls, adjustable actions, and custom actions).
## Focus Management
Focus management is where most apps fail. When a sheet, alert, or popover is dismissed, VoiceOver focus MUST return to the element that triggered it.
This section is about accessibility focus for assistive technologies. For keyboard focus, directional focus, `focusSection()`, scene-focused values, and `UIFocusGuide`, use the `focus-engine` skill.
Audit element order and grouping in [Traversal Order](#traversal-order); route keyboard-focus mechanics to `focus-engine`.
### `@AccessibilityFocusState` (iOS 15+)
`@AccessibilityFocusState` is a property wrapper that reads and writes the current accessibility focus. It works with `Bool` for single-target focus or an optional `Hashable` enum for multi-target focus.
```swift
struct ContentView: View {
@State private var showSheet = false
@AccessibilityFocusState private var focusOnTrigger: Bool
var body: some View {
Button("Open Settings") { showSheet = true }
.accessibilityFocused($focusOnTrigger)
.sheet(isPresented: $showSheet) {
SettingsSheet()
.onDisappear {
// Slight delay allows the transition to complete before moving focus
Task { @MainActor in
try? await Task.sleep(for: .milliseconds(100))
focusOnTrigger = true
}
}
}
}
}
```
### Multi-Target Focus with Enum
```swift
enum A11yFocus: Hashable {
case nameField
case emailField
case submitButton
}
struct FormView: View {
@AccessibilityFocusState private var focus: A11yFocus?
var body: some View {
Form {
TextField("Name", text: $name)
.accessibilityFocused($focus, equals: .nameField)
TextField("Email", text: $email)
.accessibilityFocused($focus, equals: .emailField)
Button("Submit") { validate() }
.accessibilityFocused($focus, equals: .submitButton)
}
}
func validate() {
if name.isEmpty {
focus = .nameField // Move VoiceOver to the invalid field
}
}
}
```
### Custom Modals
Custom overlay views need the `.isModal` trait to trap VoiceOver focus and an escape action for dismissal:
```swift
CustomDialog()
.accessibilityAddTraits(.isModal)
.accessibilityAction(.escape) { dismiss() }
```
Test dismissal as part of the modal contract: users must be able to dismiss the overlay with the relevant assistive-technology escape gesture or keyboard escape path, and focus should return to the trigger or next logical target.
### Accessibility Notifications (UIKit)
When you need to announce changes or move focus imperatively in UIKit contexts:
```swift
// Announce a status change (e.g., "Item deleted", "Upload complete")
UIAccessibility.post(notification: .announcement, argument: "Upload complete")
// Partial screen update -- move focus to a specific element
UIAccessibility.post(notification: .layoutChanged, argument: targetView)
// Full screen transition -- move focus to the new screen
UIAccessibility.post(notification: .screenChanged, argument: newScreenView)
```
## Dynamic Type
Scale text with system text styles. Scale non-text dimensions too: icon sizes, spacing, control heights, and custom hit-region dimensions should use `@ScaledMetric(relativeTo:)` where they need to track text size.
See [references/a11y-patterns.md](references/a11y-patterns.md) for Dynamic Type and adaptive layout examples, including `@ScaledMetric` and minimum tap target patterns.
## Custom Rotors
RoDiscover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.
Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.
Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.
Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, system Stop and AlarmButton secondary actions, authorization, state observation, countdown widget-extension handoff, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.
Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications, location confirmation, and full-app migration. Use when creating App Clips or wiring App Clip invocation, experience configuration, or full-app handoff.
Implement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and assistant schemas. Use when exposing app actions or entities to system surfaces.
Optimize App Store product pages for search visibility and conversion. Use for App Store Optimization (ASO), keyword research, app name/subtitle/keyword-field strategy, conversion-focused descriptions and promotional text, screenshot captions and ordering, Custom Product Pages with assigned search keywords, In-App Events, Product Page Optimization tests, localized metadata, ratings/review strategy, and in-app review prompt timing with RequestReviewAction or AppStore.requestReview. Also use when routing ASO vs App Store review, privacy/ATT, or StoreKit implementation boundaries.
Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.