Build and audit SwiftUI, UIKit, and AppKit accessibility for VoiceOver, Voice Control, Switch Control, Full Keyboard Access, Dynamic Type, focus restoration, labels/traits/actions, traversal, custom rotors, NSAccessibility, XCTest checks, adaptive system preferences, and App Store accessibility declarations. Use when implementing accessible UI, fixing an accessibility audit, testing assistive-technology behavior, or substantiating App Store Accessibility Nutrition Labels.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill ios-accessibility
Build SwiftUI, UIKit, and AppKit interfaces that remain operable with VoiceOver, Switch Control, Voice Control, Full Keyboard Access, and adaptive accessibility settings.
role, value, state, and actions.
motion, hover state, or visual arrangement the only path to meaning or action.
and dismissal.
accessibility settings.
not as a summary of isolated control-level support.
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.
See references/a11y-patterns.md for detailed SwiftUI modifier examples (labels, hints, traits, grouping, custom controls, adjustable actions, and custom actions).
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; 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.
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
}
}
}
}
}
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 overlay views need the .isModal trait to trap VoiceOver focus and an escape action for dismissal:
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.
When you need to announce changes or move focus imperatively in UIKit contexts:
// 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)
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 for Dynamic Type and adaptive layout examples, including @ScaledMetric and minimum tap target patterns.
Rotors let VoiceOver users quickly navigate to specific content types. Add custom rotors for content-heavy screens. See references/a11y-patterns.md for complete rotor examples.
Always respect these environment values:
@Environment(\.accessibilityReduceMotion) var reduceMotion
@Environment(\.accessibilityReduceTransparency) var reduceTransparency
@Environment(\.colorSchemeContrast) var contrast // .standard or .increased
@Environment(\.legibilityWeight) var legibilityWeight // .regular or .bold
Replace movement-based animations with crossfades or no animation:
withAnimation(reduceMotion ? nil : .spring()) {
showContent.toggle()
}
content.transition(reduceMotion ? .opacity : .slide)
Review every moving transition, including row deletion, quantity changes, sheet or checkout presentation, and modal dismissal. Under Reduce Motion, replace slide, bounce, parallax, spring, and large spatial transitions with opacity changes, instant state changes, or no animation.
// Solid backgrounds when transparency is reduced
.background(reduceTransparency ? Color(.systemBackground) : Color(.systemBackground).opacity(0.85))
// Stronger colors when contrast is increased
.foregroundStyle(contrast == .increased ? .primary : .secondary)
// Bold weight when system bold text is enabled
.fontWeight(legibilityWeight == .bold ? .bold : .regular)
// Decorative images: hidden from VoiceOver
Image(decorative: "background-pattern")
Image("visual-divider").accessibilityHidden(true)
// Icon next to text: Label handles this automatically
Label("Settings", systemImage: "gear")
// Icon-only buttons: MUST have an accessibility label
Button(action: { }) {
Image(systemName: "gear")
}
.accessibilityLabel("Settings")
Treat an image as decorative only when it adds no information beyond adjacent accessible text. If it communicates a product variant, state, chart point, user-generated content, or another distinguishing detail, provide a meaningful description instead of hiding it.
Voice Control relies on accessibility labels to generate spoken tap targets. If a label is missing or unspeakable, Voice Control cannot target the element.
accessibilityInputLabels as pre-freeze accessibility work for long, awkward, localized, acronym-heavy, or commonly shortened spoken labels; do not defer it as polish. Voice Control and Full Keyboard Access use these. List alternatives in descending order of importance.accessibilityInputLabels broadly to any visible target whose primary label is hard to say, including repeated row actions, quantity controls, account/settings links, media controls, and localized labels with acronyms or product names.See references/a11y-patterns.md for accessibilityInputLabels examples and speakable label guidelines.
Switch Control scans accessibility elements sequentially in reading order. Proper grouping and custom actions are critical for usability.
.accessibilityElement(children: .combine) to reduce scan stops..accessibilityAction(named:) custom actions instead — Switch Control presents them as a menu.accessibilityFrame accurately reflects the tappable region (for point scanning mode).See references/a11y-patterns.md for custom action and grouping examples.
Full Keyboard Access (iOS/iPadOS 13.4+) lets users navigate and operate an app with a hardware keyboard.
Audit whether every control is reachable, labeled, visibly focused, and operable without touch. Route Tab/directional focus, .focusable(), @FocusState, focusSection(), scene-focused values, tvOS focus, and UIFocusGuide implementation to focus-engine.
See references/a11y-patterns.md for Full Keyboard Access audit checks.
Element order and grouping must follow visual and task order across VoiceOver swipe order, Switch Control scanning, Voice Control overlays, and Full Keyboard Access review. Check missing or duplicate labels, excessive row children, hidden custom controls, focus traps, and groups whose order diverges from the task. Keep keyboard or directional routing mechanics in focus-engine.
Assistive Access provides a simplified interface for users with cognitive disabilities. Apps should support this mode:
// Check if Assistive Access is active (iOS 18+)
@Environment(\.accessibilityAssistiveAccessEnabled) var isAssistiveAccessEnabled
var body: some View {
if isAssistiveAccessEnabled {
SimplifiedContentView()
} else {
FullContentView()
}
}
Key guidelines:
For custom UIKit views, expose meaningful elements, labels, values, traits, and actions; mutate traits with insert/remove; make custom overlays modal; and use the appropriate announcement, layout-changed, or screen-changed notification. Load references/a11y-patterns.md for complete UIKit examples.
Prefer standard AppKit controls. For custom NSView or virtual elements, expose the correct NSAccessibility role, label, value, actions, and state-change notifications. Load references/a11y-patterns.md for NSAccessibilityElement and custom-control examples.
Use .accessibilityCustomContent for useful secondary facts without adding swipe stops; reserve high importance for content that should read automatically. See references/a11y-patterns.md for SwiftUI, UIKit, and AppKit examples.
For App Store accessibility nutrition labels, product-page claims, or App Store Connect accessibility answers, read references/nutrition-labels.md.
Before recommending a claim, require evidence that users can complete all common tasks with that feature on the relevant device type. Use a structured common-task by accessibility-feature matrix, include media transcripts when captions for audio-only content are relevant, and explicitly warn that App Store accessibility answers must stay accurate and must not be treated as marketing claims.
Use stable accessibility identifiers to locate XCUIElement values, then assert existence, enabled/selected state, meaningful labels/values, and hasFocus where the test environment exposes focus. Cover dismissal focus restoration, every modal exit path, and gesture alternatives. UI automation complements—not replaces—VoiceOver, Voice Control, Switch Control, keyboard, Dynamic Type, contrast, Reduce Motion, and Reduce Transparency testing. Load references/a11y-patterns.md for XCTest examples.
| Mistake | Fix |
|---|---|
| Trait assignment overwrites behavior | Insert/remove UIKit traits or use SwiftUI accessibility trait modifiers. |
| Dismissal loses focus | Return accessibility focus to the trigger. |
| Rows create excessive swipe stops | Group related children deliberately. |
| Labels repeat control type or omit icon meaning | Use a concise, speakable action/name; traits announce type. |
| Motion, text size, contrast, or transparency is fixed | Respond to the matching accessibility preference and adaptive text styles. |
| Targets are small or color-only | Provide a 44×44 target plus text, shape, or icon semantics. |
| Custom overlay is not modal | Expose modal semantics, escape action, and restoration. |
For every common task, record evidence for these gates:
alternatives for decorative, color-only, gesture-only, or adjustable content.
escape behavior; focus returns to the initiating control.
work, Switch Control exposes gesture alternatives, and Full Keyboard Access has
no unreachable controls, traps, or overridden system shortcuts.
Contrast, Bold Text, and 44x44-point targets are verified in representative
layouts and states.
and every modal exit path without substituting for manual assistive-technology
testing.
isolation boundaries are Sendable.
common-task evidence matrix for every declared device type.
Rigorous visual validation expert specializing in UI testing, design system compliance, and accessibility verification. Masters screenshot analysis, visual regression testing, and component validation. Use PROACTIVELY to verify UI modifications have achieved their intended goals through comprehensive visual analysis.
Use when you need a deterministic inspection of a WordPress repository (plugin/theme/block theme/WP core/Gutenberg/full site) including tooling/tests/version hints, and a structured JSON report to guide workflows and guardrails.
Implement accessible user interfaces with semantic HTML, keyboard navigation, sufficient color contrast, screen reader support, ARIA attributes, and proper focus management. Use this skill when creating or editing React components (.tsx, .jsx files), when implementing forms with labels and inputs, when building interactive elements (buttons, modals, menus, dialogs), when implementing keyboard navigation, when choosing colors and ensuring contrast ratios, when adding ARIA attributes, when testing with screen readers, when implementing focus states and focus management, or when creating heading structures and page landmarks.
Build mobile-first responsive layouts with fluid containers, relative units, standard breakpoints, and touch-friendly design that adapts seamlessly across devices. Use this skill when creating or modifying layouts, implementing media queries, defining breakpoints, choosing sizing units, optimizing for mobile devices, or testing UI across screen sizes. Apply when working with responsive design, mobile layouts, tablet views, desktop views, viewport configuration, or any styling that needs to adapt to different screen sizes and device capabilities.
MUST USE after building/changing any UI or when asked whether a page, component, or TUI looks right. Rigorous visual QA across web/page and terminal UIs. Prefer browser:control-in-app-browser for unauthenticated browser/page QA in Codex, then Playwright/agent-browser/dev-browser. Captures screenshot/TUI evidence with bundled diff scripts, runs design-system/functional and visual-fidelity/CJK reviewer passes, then synthesizes a good/bad verdict. Triggers: visual QA, screenshot/pixel diff, UI looks wrong, reference fidelity, design system check, responsive check, CJK text clipping, TUI alignment, box-drawing drift.
Adds interactive widget previews to the project using the previews.dart system. Use when creating new UI components or updating existing screens to ensure consistent design and interactive testing.
Use when you need a deterministic inspection of a WordPress repository (plugin/theme/block theme/WP core/Gutenberg/full site) including tooling/tests/version hints, and a structured JSON report to guide workflows and guardrails.
29 production-ready scripts for iOS app testing, building, and automation. Provides semantic UI navigation, build automation, accessibility testing, and simulator lifecycle management. Optimized for AI agents with minimal token output.
Take dpearson2699/ios-accessibility from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.