agentsclimarketplace

Ios accessibility engineering

Skill wei18/apple-dev-skills/apple-dev-skills/skills/ios-accessibility-engineering

Reusable Claude Code skills for AI-agent-driven Swift / Apple-platform development — composable via git submodule; aggregates other specialist skill repos

Install
npx -y skills add wei18/apple-dev-skills --skill ios-accessibility-engineering

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Concrete VoiceOver / Dynamic Type / touch-target / Reduce Motion implementation guide for SwiftUI and UIKit. Invoke when building or auditing any user-facing iOS/macOS UI, when asked "make this accessible", or before an App Review submission a11y pass.

SKILL.md

9.1 KB, as published. Nobody here has run it

iOS Accessibility Engineering

When to invoke

  • Adding or modifying any user-facing View, screen, or interactive control.
  • Running a pre-submission accessibility audit against App Store Review guidelines.
  • User says "make this accessible", "check a11y", "VoiceOver doesn't read this", or "does this pass WCAG".
  • Reviewing a PR that introduces new SwiftUI View or UIKit UIView / UIViewController code.

VoiceOver: labelling and semantics

Labels, values, and hints are three distinct channels:

  • .accessibilityLabel("Done") — the noun identifying the element. Keep it short; VoiceOver reads it first.
  • .accessibilityValue("3 of 9") — the current state or quantity. Changes without re-reading the label.
  • .accessibilityHint("Double-tap to submit") — what happens on activation. Users can turn hints off; never put essential info here.

In UIKit, set accessibilityLabel, accessibilityValue, and accessibilityHint on any UIView. In SwiftUI, use the .accessibilityLabel(_:), .accessibilityValue(_:), and .accessibilityHint(_:) modifiers.

Traits communicate the element's role and state. Common SwiftUI traits:

.accessibilityAddTraits(.isButton)     // tappable action
.accessibilityAddTraits(.isHeader)     // section heading — VoiceOver lets users jump by heading
.accessibilityAddTraits(.updatesFrequently)  // live score, timer — suppresses constant interruptions
.accessibilityAddTraits(.isSelected)   // toggle / tab selection state

Grouping — combine several sub-views into one focusable element so VoiceOver reads it as a single sentence:

HStack { thumbnail; title; subtitle }
    .accessibilityElement(children: .combine)

Use .accessibilityElement(children: .ignore) when the children are redundant and you supply a custom label on the container. Use .accessibilityElement(children: .contain) to keep individual children focusable inside a group (e.g. a toolbar).

Hiding decorative content:

Image("confetti-background")
    .accessibilityHidden(true)   // purely decorative; skip in VoiceOver rotor

Meaningful images need a label: Image("trophy").accessibilityLabel("Achievement unlocked").

Announcing dynamic changes — when content updates in place without a navigation event:

AccessibilityNotification.Announcement("Level complete").post()
// or for a layout change:
AccessibilityNotification.LayoutChanged(element: focusTarget).post()
// or for a screen change (modal, full replacement):
AccessibilityNotification.ScreenChanged(element: focusTarget).post()

In UIKit: UIAccessibility.post(notification: .announcement, argument: "Level complete").

Dynamic Type

  • Use Font.body, .headline, .caption etc. (text styles), or UIFont.preferredFont(forTextStyle:) in UIKit. Never hard-code Font.system(size: 17) without a text style.
  • SwiftUI text styles scale automatically. If a control must cap scaling (compact digit grids, icon labels), apply .dynamicTypeSize(...DynamicTypeSize.xLarge) — this clamps only sizes above .xLarge; default .large stays byte-identical and snapshot baselines are unaffected.
  • Test at AX5 (accessibility-extra-extra-extra-large) in Simulator: xcrun simctl ui <udid> content_size accessibility-extra-extra-extra-large.
  • minimumScaleFactor is width-only — it won't rescue a glyph that overflows vertically (a tall digit clips blank in a short cell); cap the size instead. In fullScreenCover / sheet modals @Environment(\.dynamicTypeSize) can read stale — use geometry-driven layout (ViewThatFits). See swiftui-interaction-footguns for both traps in full.
  • Avoid fixed frame(height:) on text containers; prefer frame(minHeight:) with unlimited vertical growth.

Touch targets

  • Apple HIG minimum: 44 × 44 pt. A visually small button (e.g. a 20pt icon) passes if its hit region is padded to 44pt.
  • .contentShape(Rectangle()) enlarges the hit region for .buttonStyle(.plain) containers or custom onTapGesture views where Spacers don't automatically expand the hit area.
  • Voice Control and Switch Control rely on accessibilityLabel to identify targets by name; if two same-named buttons exist on screen, add .accessibilityInputLabels(["Submit order", "Submit"]) to disambiguate.

Motion, transparency, and contrast

@Environment(\.accessibilityReduceMotion) var reduceMotion
// Skip or replace animations when true:
withAnimation(reduceMotion ? nil : .easeInOut) { state.toggle() }
  • \.accessibilityReduceTransparency — remove blur / frosted-glass effects when true; use an opaque fill instead. SwiftUI .background(.ultraThinMaterial) does NOT automatically drop its blur when Reduce Transparency is on — you must branch on \.accessibilityReduceTransparency manually and substitute a solid background.
  • \.accessibilityDifferentiateWithoutColor — never rely on color alone to convey state; add an icon or label.
  • \.colorSchemeContrast (.increased) — if you draw custom backgrounds, check this and raise contrast when set.

Verification and testing

Accessibility Inspector (Xcode → Open Developer Tool → Accessibility Inspector): point the inspector at your app in Simulator, run the automated audit (the triangle icon), and fix every reported issue before submission. It surfaces missing labels, low-contrast text, small touch targets, and missing traits.

Snapshot tests do not verify Dynamic Type or VoiceOver. An NSHostingView in a headless test process has no live AX client; accessibilityLabel / accessibilityChildren traversal returns empty trees. Injecting DynamicTypeSize.accessibility3 into an NSHostingView bypasses the modal env-propagation path, giving a false pass. Reliable verification requires a booted simulator with idb or xcrun simctl:

# Set content size to AX5 and screenshot
xcrun simctl ui <udid> content_size accessibility-extra-extra-extra-large
idb screenshot <udid> after-ax5.png
# Tap through the UI with VoiceOver via idb ui_tap / ui_describe_all

CI a11y gate — CVS Health's a11y-audit (open source, Swift-based) provides a programmatic audit runner that can fail CI on missing labels or contrast violations; treat it as a complementary gate, not a replacement for manual Accessibility Inspector review.

WCAG 2.2 mapping for App Review

WCAG criterionWhat it requiresHow it surfaces in iOS
1.1.1 Non-text contentMeaningful images have text alternativesaccessibilityLabel on Image
1.4.3 Contrast (minimum)≥ 4.5:1 for normal text, 3:1 for large textCheck in Accessibility Inspector
1.4.4 Resize textText reflows up to 200% without loss of contentDynamic Type + ViewThatFits
2.5.8 Target size (Minimum) — AAInteractive targets ≥ 24×24 CSS px (WCAG 2.2 new AA criterion).contentShape + padding; the AA conformance gate
2.5.5 Target size — AAAInteractive targets ≥ 44×44 CSS px (≈44pt on 1× devices)Apple HIG minimum; stronger than AA — aim for this

App Review does not formally audit against WCAG, but the Human Interface Guidelines cite these thresholds and reviewers reject apps that are obviously unusable with VoiceOver or at accessibility text sizes.

Verification checklist

  • All interactive controls have an accessibilityLabel; decorative images have accessibilityHidden(true).
  • Dynamic type tested at AX5 on a booted simulator — not only at default .large.
  • No fixed frame(height:) on text containers; minimumScaleFactor is not used as a substitute for layout flexibility.
  • Touch targets ≥ 44pt; .contentShape applied wherever Spacers or padding would otherwise shrink the hit region.
  • AccessibilityNotification posted for in-place content changes.
  • accessibilityReduceMotion checked before all non-trivial animations.
  • Accessibility Inspector automated audit passes with zero errors.
  • VoiceOver reading order verified manually (not inferred from visual order alone).

Related skills

  • swiftui-interaction-footguns: Dynamic Type / modal env footguns and the minimumScaleFactor pitfall in detail.
  • swift-testing-baseline: headless AX-tree limitation and why sim verification is the reliable gate.

External references

  • dadederk/iOS-Accessibility-Agent-Skill (Daniel Devesa Derksen-Staats, MIT) — a complementary, high-authority a11y skill. It goes deeper on Large Content Viewer (UILargeContentViewerItem), accessibilityRotor / accessibilityRepresentation / AccessibilityFocusState, Full Keyboard Access, and VoiceOver custom actions — areas this skill keeps brief. This skill's strength is the runtime-verification pitfalls (the minimumScaleFactor vertical-clip trap, headless-AX-tree false passes, idb/simctl sim-verify, WCAG 2.2 mapping). Use both.

Gives 0 of the 12 instructions most accessibility skills give

Counted across 384 of the 385 authors here whose files we hold, read 2026-08-06

  • maintain visible focus indicatorsin 46 of 384, across 33 files
  • run automated accessibility scansin 44 of 384, across 32 files
  • make all interactive elements keyboard reachablein 39 of 384, across 32 files
  • use semantic HTML before ARIAin 38 of 384
  • respect reduced motion preferencesin 35 of 384, across 27 files
  • Associate labels programmatically with inputsin 33 of 384, across 26 files
  • Use native elements over ARIAin 30 of 384, across 18 files
  • test with a screen readerin 26 of 384, across 22 files
  • map findings to WCAG criteriain 24 of 384, across 14 files
  • meet minimum color contrast ratiosin 24 of 384, across 16 files
  • Provide text alternatives for imagesin 24 of 384, across 18 files
  • trap focus inside open modalsin 23 of 384, across 18 files

Said here and by no other author read

  • avoid fixed heights on text containers
  • pad touch targets to 44x44pt
  • branch animations on reduce motion preference
  • verify dynamic type at AX5 on booted simulator
  • post accessibility notifications for in-place content changes
  • verify voiceover reading order manually

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.