.agents/skills/ios-accessibility/SKILL.md
Implement, review, or improve accessibility in iOS/macOS apps with SwiftUI and UIKit. Use when adding VoiceOver support with accessibility labels, hints, values, and traits; when grouping or reordering accessibility elements; when managing focus with @AccessibilityFocusState; when supporting Dynamic Type with @ScaledMetric; when building custom rotors or accessibility actions; when auditing a11y compliance; or when adapting UI for assistive technologies and system accessibility preferences.
npx skillsauth add DFly7/iOS-FastAPI-Supabase-AI ios-accessibilityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
@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 .bold
Replace 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")
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 = true
See references/a11y-patterns.md for UIKit 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
)
.accessibilityTraits(.isButton) overwrites all existing traits. Use .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 boundariesdevelopment
Resolve Swift concurrency compiler errors, adopt approachable concurrency (SE-0466), and write data-race-safe async code. Use when fixing Sendable conformance errors, actor isolation warnings, or strict concurrency diagnostics; when adopting default MainActor isolation, @concurrent, nonisolated(nonsending), or Task.immediate; when designing actor-based architectures, structured concurrency with TaskGroup, or background work offloading; or when migrating from @preconcurrency to full Swift 6 strict concurrency.
development
Implement Swift Codable models for JSON and property-list encoding and decoding with JSONDecoder, JSONEncoder, CodingKeys, and custom init(from:) or encode(to:). Use when parsing API responses, remapping keys, flattening nested JSON, handling date or data decoding strategies, decoding heterogeneous arrays, or integrating Codable with URLSession, SwiftData, or UserDefaults.
development
Implement, review, or improve data visualizations using Swift Charts. Use when building bar, line, area, point, pie, or donut charts; when adding chart selection, scrolling, or annotations; when plotting functions with vectorized BarPlot, LinePlot, AreaPlot, or PointPlot; when customizing axes, scales, legends, or foregroundStyle grouping; or when creating specialized visualizations like heat maps, Gantt charts, stacked/grouped bars, sparklines, or threshold lines.
data-ai
Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.