Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Every user-facing view must be usable with VoiceOver, Switch Control, Voice Control, Full Keyboard Access, and other assistive technologies. This skill covers the patterns and APIs required to build accessible iOS apps.
.accessibilityLabel..accessibilityAddTraits (never direct assignment).@ScaledMetric, adaptive layouts).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.
@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() }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)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 .boldReplace movement-based animations with crossfades or no animation:
withAnimation(reduceMotion ? nil : .spring()) {
showContent.toggle()
}
content.transition(reduceMotion ? .opacity : .slide)// 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")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 (iOS 14+). Voice Control and Full Keyboard Access use these. List alternatives in descending order of importance.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+) provides Tab/Shift-Tab navigation, arrow keys, Space/Enter activation, and Escape for dismissal. Standard SwiftUI controls are focusable by default.
.focusable() (iOS 17+) to make custom views participate in the focus system. The focusable(_:interactions:) variant controls whether the view supports .activate, .edit, or both.@FocusState to track and programmatically move keyboard focus..keyboardShortcut() to frequently used actions. Do not override system-defined shortcuts (Cmd+C, Cmd+V, Cmd+Tab, etc.).@FocusState + .focused($isFocused) if a custom view needs to adjust its appearance when focused.See references/a11y-patterns.md for .focusable(), FocusInteractions, keyboard shortcut, and multi-field focus examples.
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:
When working with UIKit views:
isAccessibilityElement = true on meaningful custom views.accessibilityLabel on all interactive elements without visible text..insert() and .remove() for trait modification (not direct assignment).accessibilityViewIsModal = true on custom overlay views to trap focus..announcement for transient status messages..layoutChanged with a target view for partial screen updates..screenChanged for full screen transitions.// UIKit trait modification
customButton.accessibilityTraits.insert(.button)
customButton.accessibilityTraits.remove(.staticText)
// Modal overlay
overlayView.accessibilityViewIsModal = trueAppKit accessibility uses NSAccessibilityProtocol and related role-specific protocols to describe accessible elements. Standard AppKit controls already provide much of this behavior; customize labels, values, roles, and actions only when the defaults are insufficient.
NSView subclasses, adopt the appropriate role-specific accessibility behavior and return the correct role, label, value, and actions.NSAccessibilityElement for accessible items that are not backed by their own NSView.NSAccessibility notifications when state changes need to be announced to assistive apps.final class FavoriteToggleView: NSView {
var isFavorite = false {
didSet {
NSAccessibility.post(element: self, notification: .valueChanged)
}
}
override func isAccessibilityElement() -> Bool { true }
override func accessibilityRole() -> NSAccessibility.Role? { .button }
override func accessibilityLabel() -> String? { "Favorite" }
override func accessibilityValue() -> Any? { isFavorite ? "On" : "Off" }
override func accessibilityPerformPress() -> Bool {
isFavorite.toggle()
return true
}
}See references/a11y-patterns.md for AppKit examples including NSAccessibilityElement and announcement notifications.
See references/a11y-patterns.md for UIKit and AppKit accessibility patterns and custom content examples.
ProductRow(product: product)
.accessibilityCustomContent("Price", product.formattedPrice)
.accessibilityCustomContent("Rating", "\(product.rating) out of 5")
.accessibilityCustomContent(
"Availability",
product.inStock ? "In stock" : "Out of stock",
importance: .high // .high reads automatically with the element
)Use XCUIElement accessibility attributes to write UI tests that verify accessibility properties:
func testProductRowAccessibility() throws {
let app = XCUIApplication()
app.launch()
let productCell = app.cells["product-organic-apples"]
XCTAssertTrue(productCell.exists)
XCTAssertTrue(productCell.isEnabled)
// Verify the label is set and meaningful
XCTAssertFalse(productCell.label.isEmpty)
// Verify a specific element has the expected label
let favoriteButton = productCell.buttons["Favorite"]
XCTAssertTrue(favoriteButton.exists)
XCTAssertTrue(favoriteButton.isEnabled)
}Key XCUIElementAttributes properties for accessibility verification: label, identifier, value, isEnabled, hasFocus, isSelected, placeholderValue, title.
Test dismissal focus restoration:
func testSheetDismissReturnsFocus() throws {
let app = XCUIApplication()
app.launch()
let triggerButton = app.buttons["Open Settings"]
triggerButton.tap()
// Dismiss the sheet
let doneButton = app.buttons["Done"]
doneButton.tap()
// Verify focus returns to trigger (in accessibility-focused testing)
XCTAssertTrue(triggerButton.hasFocus)
}.accessibilityAddTraits(.isButton)..accessibilityElement(children: .combine)..accessibilityLabel("Settings button") reads as "Settings button, button." Omit the type.Image-only button MUST have .accessibilityLabel.accessibilityReduceMotion before movement animations..font(.system(size: 16)) ignores Dynamic Type. Use .font(.body) or similar text styles.frame(minWidth: 44, minHeight: 44) and .contentShape()..isModal on overlays: Custom modals without .accessibilityAddTraits(.isModal) let VoiceOver escape.For every user-facing view, verify:
.accessibilityAddTraitsImage(decorative:) or .accessibilityHidden(true)).accessibilityElement(children: .combine).isModal trait and escape action@ScaledMetric, system fonts, adaptive layouts)Sendable when passed across concurrency boundariesaccessibilityInputLabels provided for elements with long or awkward primary labels.focusable() when they should participate in Full Keyboard Access navigationskills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references