Mobile Accessibility skill for Codex
Every core task, eyes closed, text at 200%. Codex audits core user tasks with the screen reader and the largest text size rather than only running a scanner, then fixes semantics in the stack's own API: SwiftUI, UIKit, Compose, Views, React Native or Flutter. It groups cards into single focus stops with custom actions, enforces 44pt and 48dp targets, wires performAccessibilityAudit and Espresso checks into tests, and reports findings against WCAG 2.2.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- mobile-accessibility Installs to ~/.agents/skills/mobile-accessibility/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install mobile-accessibility The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project mobile-accessibility Installs to .agents/skills/mobile-accessibility/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/mobile-accessibility.zipunzip -o mobile-accessibility.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) mobile-accessibility PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $mobile-accessibility, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Audits and fixes accessibility in iOS, Android, React Native and Flutter apps - VoiceOver and TalkBack labels, traits and focus order, Dynamic Type and Android font scaling to 200 percent, touch target sizes, color contrast, Reduce Motion, and automated checks with XCUITest performAccessibilityAudit, Accessibility Scanner and Espresso checks. Maps findings to WCAG 2.2 for compliance work such as the European Accessibility Act. Use when asked for an accessibility audit, when screen reader users report problems, or before a release that must meet WCAG.
Use it when
- Running an accessibility audit before a release or for EAA or WCAG compliance
- Screen reader users report unlabeled or unreachable controls
- Text scaling truncates or overlaps content
- Adding accessibility checks to UI test suites
Use something else when
- The UI is a website or web view rather than native screens: use wcag-accessibility-audit
- You need ARIA widget patterns for web components: use aria-patterns
How to check the result
performAccessibilityAudit and Espresso or Compose accessibility checks pass, and a manual VoiceOver and TalkBack walk of each core task succeeds at the largest text size.
The mistake it prevents
Fixing only what an automated scanner reports, while focus order, grouping and announcements that make a flow usable go untested.
Done well means: a screen reader user can complete every core task without sighted help, text scales to the largest setting without truncating meaning, every control is at least 44x44 pt on iOS and 48x48 dp on Android, contrast meets WCAG AA, and automated checks run in CI so regressions fail the build.
Before changing anything
- Read
AGENTS.md, then identify the UI stack per screen: SwiftUI, UIKit, Jetpack Compose, Android Views, React Native or Flutter. APIs differ by stack; fixes must use the stack’s own semantics, not overlays. - List the core tasks (sign in, search, purchase, settings) and audit those first.
- Check whether the design system defines text styles and color tokens; most fixes belong there, not in individual screens.
Audit workflow
- Run automated checks (below) to clear the mechanical issues
- Walk each core task with VoiceOver (iOS) and TalkBack (Android), eyes off the screen
- Repeat at the largest text size (iOS Accessibility sizes, Android font scale 2.0)
- Check contrast of text and icons against their actual backgrounds, both themes
- Turn on Reduce Motion and confirm animations calm down
- Use Switch Control or a hardware keyboard to confirm focus reaches every control
- Record each finding with screen, element, WCAG criterion, severity and fix
Thresholds
| Check | Target | Source |
|---|---|---|
| Touch target | 44x44 pt iOS, 48x48 dp Android (visual can be smaller if the hit area is not) | Apple HIG, Material |
| Minimum target, WCAG | 24x24 CSS px or adequate spacing | WCAG 2.2 SC 2.5.8 (AA) |
| Text contrast | 4.5:1 normal, 3:1 large (18 pt, or 14 pt bold) | SC 1.4.3 |
| Icons, borders, focus indicators | 3:1 against adjacent colors | SC 1.4.11 |
| Text resize | Usable at 200 percent | SC 1.4.4 |
| Motion | Respect the OS reduce-motion setting | SC 2.3.3 (AAA), platform guidance |
Screen reader semantics
Every interactive element needs a name, a role and a state, announced in that order.
| Need | SwiftUI | UIKit | Compose | Views | React Native | Flutter |
|---|---|---|---|---|---|---|
| Name | .accessibilityLabel("Delete") | accessibilityLabel | contentDescription in semantics | contentDescription | accessibilityLabel | Semantics(label:) |
| Role | .accessibilityAddTraits(.isButton) | accessibilityTraits | role = Role.Button | AccessibilityDelegateCompat / widget type | accessibilityRole | Semantics(button: true) |
| State | .accessibilityValue, .isSelected trait | accessibilityValue | stateDescription, selected | stateDescription | accessibilityState | Semantics(checked:, selected:) |
| Group | .accessibilityElement(children: .combine) | container with one element | Modifier.semantics(mergeDescendants = true) {} | focusable parent + screenReaderFocusable | accessible={true} on parent | MergeSemantics |
| Hide decoration | .accessibilityHidden(true) | isAccessibilityElement = false | Modifier.clearAndSetSemantics {} | importantForAccessibility="no" | accessibilityElementsHidden / importantForAccessibility | ExcludeSemantics |
| Heading | .accessibilityAddTraits(.isHeader) | .header trait | heading() | accessibilityHeading | accessibilityRole="header" | Semantics(header: true) |
Rules:
- Labels describe purpose, not appearance: “Delete message”, not “trash icon”. Do not include the role (“button”) in the label; the screen reader adds it.
- Group a card into one element with a combined label and expose secondary actions as
custom actions (
.accessibilityAction(named:), ComposecustomActions) instead of several tiny focus stops. - Announce async changes: iOS
AccessibilityNotification.Announcement("Saved").post(), ComposeliveRegion = LiveRegionMode.Polite, RNAccessibilityInfo.announceForAccessibility. - Move focus to new content after navigation or when a modal opens, and return it on close.
- Images that convey information get a label; decorative images are hidden.
- Custom gestures (swipe to delete, long press) need an equivalent accessible action.
HStack {
AsyncImage(url: item.thumbnail).accessibilityHidden(true)
VStack(alignment: .leading) {
Text(item.title).font(.headline)
Text(item.price, format: .currency(code: "EUR")).font(.subheadline)
}
}
.accessibilityElement(children: .combine)
.accessibilityAction(named: "Add to cart") { cart.add(item) }
Row(
Modifier
.semantics(mergeDescendants = true) {
customActions = listOf(CustomAccessibilityAction("Add to cart") { cart.add(item); true })
}
.clickable(onClickLabel = "Open product") { open(item) }
) { /* image with contentDescription = null, title, price */ }
Text scaling
- iOS: use text styles (
.font(.body),UIFont.preferredFont(forTextStyle:)withadjustsFontForContentSizeCategory = true). Scale custom sizes and spacing with@ScaledMetric. Switch horizontal layouts to vertical whendynamicTypeSize.isAccessibilitySize. - Android: text in
sp, neverdp; Android 14 scales nonlinearly up to 200 percent. Avoid fixed-height containers around text; let rows grow. - React Native: keep
allowFontScalingon; cap extremes withmaxFontSizeMultiplieronly where layout truly cannot adapt. Flutter: respectMediaQuery.textScalerOf(context). - Never truncate essential text to one line at large sizes; wrap or reflow.
Touch targets and contrast
- Expand hit areas without growing visuals: SwiftUI
.contentShape(Rectangle())with a.frame(minWidth: 44, minHeight: 44); ComposeModifier.minimumInteractiveComponentSize()(Material components apply it already); ViewsTouchDelegateor padding; RNhitSlop. - Compute contrast on the rendered colors, including text over images and gradients (add a scrim). Placeholder text and disabled-looking active controls are common failures.
- Never use color alone to convey state; add an icon, text or shape.
Automated checks
// XCUITest (Xcode 15+ / iOS 17+)
func testCheckoutAccessibility() throws {
let app = XCUIApplication(); app.launch()
app.buttons["Checkout"].tap()
try app.performAccessibilityAudit()
}
// Espresso with Android Views: enable once in the test class
@Before fun setUp() { AccessibilityChecks.enable().setRunChecksFromRootView(true) }
- Android: Accessibility Scanner app for manual sweeps; Compose UI tests can run the same
accessibility checks in recent
ui-testversions (check the release notes for the enabling API in your version). Android Lint flags missingcontentDescription. - React Native:
eslint-plugin-react-native-a11y; Flutter:meetsGuideline(androidTapTargetGuideline),textContrastGuidelineandlabeledTapTargetGuidelinein widget tests. - Automated checks catch perhaps a third of issues. Manual screen reader passes are required.
Useful device commands:
adb shell settings put system font_scale 2.0 # restore with 1.0
xcrun simctl ui booted content_size accessibility-extra-extra-extra-large
Gotchas
accessibilityIdentifier/testTagis for tests and is never read aloud.- A SwiftUI
Buttonwith only anImage(systemName:)reads the symbol name; always give it a label. - Custom tab bars and bottom sheets often trap or lose focus; test them explicitly.
- Web content in a WebView follows web rules (ARIA); audit it separately with wcag-accessibility-audit.
- Compliance (EAA since June 2025, ADA, Section 508) is a legal judgment; report WCAG conformance findings and let counsel decide on claims.
Output
## Accessibility audit: <app> <version>, <platforms>
| # | Screen | Element | Issue | WCAG | Severity | Fix |
|---|---|---|---|---|---|---|
**Automated:** <tools run, issue counts before/after>
**Manual:** <VoiceOver/TalkBack task walk results, largest text size result>
**Not tested:** <screens, devices, assistive tech>
Severity: blocker (task impossible), major (task possible with significant effort), minor.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Mobile Accessibility"
short_description: "Every core task, eyes closed, text at 200%"
default_prompt: "$mobile-accessibility Audit the checkout flow for VoiceOver and TalkBack and fix what you find" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.