accessibility-as-code
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
Skill by zakariaf
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/zakariaf-accessibility-as-code
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/accessibility-as-code
What's inside
- Skill name
accessibility-as-code- Description
- Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack. Use when adding a pressable, swipe or a11y label, text clips when large, contrast fails or Reduce Motion is missed. Not for RTL (rtl-and-direction).
- License
- MIT
- Instructions
- 227 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (72)
- SKILL.md
- shared.json · assets/
- accessible-task-row.md · examples/
- contrast-and-colour.md · references/
- roles-names-and-screen-readers.md · references/
- testing-accessibility.md · references/
- text-scaling-and-motion.md · references/
- check-a11y-code.mjs · scripts/
- check-contrast.mjs · scripts/
- check-lib.mjs · scripts/
- load-ts-constants.mjs · scripts/lib/
- object-literal.mjs · scripts/lib/
- source-scan.mjs · scripts/lib/
- wcag.mjs · scripts/lib/
- selftest.mjs · scripts/
- 01-large-text-core-screens.yaml · templates/.maestro/flows/a11y/
- contrast.json · templates/contracts/
- contrast.schema.json · templates/contracts/
- contrast.ts · templates/src/theme/
- palette.ts · templates/src/theme/
- theme-types.ts · templates/src/theme/
- motion-config.test.tsx · templates/src/ui/a11y/
- motion-config.tsx · templates/src/ui/a11y/
- system-a11y-store.test.ts · templates/src/ui/a11y/
- system-a11y-store.ts · templates/src/ui/a11y/
- use-announce.test.ts · templates/src/ui/a11y/
- use-announce.ts · templates/src/ui/a11y/
- use-reduce-motion.test.tsx · templates/src/ui/a11y/
- use-reduce-motion.ts · templates/src/ui/a11y/
- app-contrast.test.ts · templates/test/a11y/
- contrast-contract.test.ts · templates/test/a11y/
- contrast-contract.ts · templates/test/a11y/
- cvd.ts · templates/test/a11y/
- find-inaccessible-pressables.test.tsx · templates/test/a11y/
- find-inaccessible-pressables.ts · templates/test/a11y/
- bad-button.tsx · tests/bases/a11y-app/.agents/skills/some-skill/tests/fixtures/bad/src/ui/
- package.json · tests/bases/a11y-app/
- quality-gates.json · tests/bases/a11y-app/
- bad-button.tsx · tests/bases/a11y-app/skills/other-skill/templates/src/ui/
- reset-data-sheet.tsx · tests/bases/a11y-app/src/features/settings/components/
- progress-chart.tsx · tests/bases/a11y-app/src/features/stats/components/
- delete-task-dialog.tsx · tests/bases/a11y-app/src/features/tasks/components/
- task-card.tsx · tests/bases/a11y-app/src/features/tasks/components/
- tasks-screen.test.tsx · tests/bases/a11y-app/src/features/tasks/
- action-card.tsx · tests/bases/a11y-app/src/ui/
- app-text.tsx · tests/bases/a11y-app/src/ui/
- button.tsx · tests/bases/a11y-app/src/ui/
- confetti.tsx · tests/bases/a11y-app/src/ui/
- dialog-card.tsx · tests/bases/a11y-app/src/ui/
- dialog-frame.tsx · tests/bases/a11y-app/src/ui/
- hold-button.tsx · tests/bases/a11y-app/src/ui/
- icon-button.tsx · tests/bases/a11y-app/src/ui/
- icon.tsx · tests/bases/a11y-app/src/ui/
- list-row.tsx · tests/bases/a11y-app/src/ui/
- overlay.tsx · tests/bases/a11y-app/src/ui/
- pressable-surface.tsx · tests/bases/a11y-app/src/ui/
- slider.tsx · tests/bases/a11y-app/src/ui/
- touch-size.ts · tests/bases/a11y-app/src/ui/
- use-hold-to-confirm.ts · tests/bases/a11y-app/src/ui/
- bad-button.tsx · tests/bases/a11y-app/src/vendor/kit/
- …and 12 more
SKILL.md (first lines)
# Accessibility as code
Every control is reachable and named for VoiceOver and TalkBack in every locale the app ships,
every target's own box is at least 44 pt, text grows to 200 % without clipping, every colour pair
the UI and its canvases paint is declared once and meets WCAG 2.2 AA in every colour set, motion
follows one live reduce-motion source, every swipe, drag or hold has a tap or a named action, custom
canvases speak a summary, and overlays keep both screen readers inside.
Scripts and tests prove what a machine can see; a human screen-reader pass covers the rest.
## Rules that must hold
1. **Give every interactive element a role and a translated name** (`t()`, as `accessibilityLabel`
or `aria-label`; `aria-labelledby` alone names it on Android and the web only), a hint only when
the outcome is not obvious (the result, never the gesture), and its state (`accessibilityState`
or `aria-*`; React Native's `Pressable` and Touchables derive `disabled` themselves, Gesture
Handler's do not). A pressable no screen reader should focus (a backdrop) is `accessible={false}`
and its action exists elsewhere. Why: both screen readers read role, name and state, and
`getByRole` finds exactly that.
2. **Make every touch target's own box at least 44 × 44 pt (`a11y.minTouchPt`, 48 for Material);
never `hitSlop`.** Custom hit regions prove their smallest cell in a unit test. Why: `hitSlop`
is invisible in layout, overlaps neighbours and does not move the screen reader's focus frame.
3. **Let text grow to at least 200 %:** never `allowFontScaling={false}`; any
`maxFontSizeMultiplier` sits on the text primitive and is never below the cap (`a11y.maxFontScale`,
default 2); `minHeight` instead of `height`; no `numberOfLines` on sentences; rows stack when
`fontScale >= 1.25` (iOS XXXL and up, Android 1.3 and up: Android reports 1.3 as 1.2999999523).
Why: WCAG 1.4.4, and a clipped or truncated label is unreadable.
4. **Declare every colour pair the UI paints in `contracts/contrast.json`, the app's one pair table
(the theme palette and every other colour table: charts, tags; a Skia canvas surface keeps its
pairs in its own `canvas-contrast.json`), and meet WCAG 2.2 AA in every colour set:** text 4.5:1,
icons, edges, focus and chart lines 3:1, a filled shape 3:1 off its edge or its ground;
translucent colours composited first. Why: a palette never says which colour sits on which, and
design-system-structure's palette tools read the same table, so it is kept once.
5. **Never convey meaning by colour alone.** Every state carries an icon, shape or word, and
colours that alone tell categories apart (`distinct` lists: chart series, tags) stay 0.07 apart
in OKLab under protanopia, deuteranopia and tritanopia. Why: about 1 in 12 men cannot tell
red from green, and status colours collide (0.013 apart under deuteranopia).
6. **Reduce motion only through `useReduceMotion()` and `<MotionConfig>`**; never Reanimated's
`useReducedMotion()` or `AccessibilityInfo` outside `systemA11yStore`; every moment that moves
has a reduced alternative; functional timers pass `ReduceMotion.Never`. Why: Reanimated's hook
is a module-load constant that misses live changes and the in-app setting.
7. **Expose each information-bearing canvas as one image** labelled with `t()` of a pure
`describe(state)`, or mark it decorative; **announce events with `useAnnounce()`** (one short
sentence per action, only while a screen reader runs, queued so a screen change cannot cut it
off; a live region for status text on Android). Why: Skia content is invisible to screen
readers, and stray announcements talk over the user.
8. **Keep overlays (dialogs, sheets, menus, popovers, drawers) modal on both platforms and reading
order visual:** Expo Router modal routes or RN `<Modal>` first; an in-tree overlay sets
`accessibilityViewIsModal` on its root (the scrim, never the card; `aria-modal` works only on
`Pressable` and TouchableOpacity/Highlight/NativeFeedback, and on the web) and hides what is
behind it on Android (`aria-hidden` on the screen's side, not inside the layer) in the same
component; an in-tree overlay host wraps the navigator, so the header and tab bar are hidden too.
Never nest a pressable in another: a card's opener and action are siblings, and the text layer
drawn above the opener has `pointerEvents: 'none'`, both in one shared `src/ui/` card component
(only `src/ui/` may import `Pressable`). Sliders are adjustable with declared
actions; other-language text sets `accessibilityLanguage`. Why: otherwise focus wanders behind
dialogs, inner actions are unreachable, taps on a card's text do nothing and sliders cannot move.
9. **Give every gesture-only action (long press, swipe, drag, pinch) one tap or a named action:**
`accessibilityActions` with translated labels and `onAccessibilityAction` on the focused element
(the VoiceOver Actions rotor, the TalkBack actions menu), plus a single-pointer way for drags. An
essential gesture is a reviewed `a11yGestureExceptions` entry, never a dummy action. Why: both
screen readers take over swipes to move focus (WCAG 2.5.1, 2.5.7).
10. **Query by role and name in tests, and end every screen test with the
`findInaccessiblePressables` audit.** Why: a missing role or name then fails a test, not a user.
11. **Run a human VoiceOver and TalkBack pass on every release candidate**, in one left-to-right
and (when shipped) one right-to-left locale; ask for it, never claim it, and turn every reported
problem into a failing test first. Why: no checker can hear whether the spoken output makes sense.
12. **Never weaken a pair, a gate or a check to go green.** An accepted failure is a contract
exception with a `reason` and `approvedBy`, added only after the developer agrees, and printed
on every run. Why: a silenced contrast failure ships unreadable text.
## Workflow
1. **First time.** Copy `templates/src/ui/a11y/` to `src/ui/a11y/` and `templates/test/a11y/` to
`test/a11y/`. Start the store once in the root layout (`src/app/_layout.tsx`):
`useEffect(() => watchSystemA11y(reportError), [])`, and wrap the app in
`<MotionConfig preference={reduceMotionPreference}>` (`"system"` when the app has no setting;
settings-and-preferences owns a saved one); render everything that reads `useReduceMotion()`
below it. motion-and-animation's `check-motion` proves this wiring (`reduce-motion-root`).
Make sure `quality-gates.json` has the `a11y` block (`minTouchPt` 44, `maxFontScale` 2,
`textContrastMin` 4.5, `nonTextContrastMin` 3, `minCvdDistanceOk` 0.07; quality-gates owns
the file).
2. **Contrast contract.** Copy `templates/contracts/contrast.json` and its schema to `contracts/`.
Its `app` entry describes design-system-structure's palette (`src/theme/palette.ts`, synced here
with `contrast.ts` and `theme-types.ts`; copy those three only when the app has none), and that
skill's `write-palette` and `check-design-system` read the same entry. Add a pair whenever a
component puts a colour on a new ground (a label over a growing fill needs one per ground), and a
palette entry for every other colour table (charts, tags). Read
[references/contrast-and-colour.md](references/contrast-and-colour.md).
3. **Building or changing a component or screen.** Read
[references/roles-names-and-screen-readers.md](references/roles-names-and-screen-readers.md)
and imitate [examples/accessible-task-row.md](examples/accessible-task-row.md): shared `src/ui/`
controls, names from `t()` in the model hook, the right role and state, a 44 pt box, headers via
the text primitive, a card's opener and action as siblings with the text layer passing touches
through (opener and text layer in one `src/ui/` card the feature composes), every swipe or drag
action also a named accessibility action on a shared row, overlays modal on both platforms.
4. **Large text and motion.** Read
[references/text-scaling-and-motion.md](references/text-scaling-and-motion.md): stack at large
text, `minHeight` everywhere, taller line heights for scripts with tall glyphs, and the reduced
alternative of each moving moment.
5. **Custom canvases and announcements.** Label each canvas from a pure `describe(state)` through
`t()` and unit-test `describe()`; announce results with `useAnnounce()`, once per action.
6. **Tests.** Read [references/testing-accessibility.md](references/testing-accessibility.md).
Query by role and name, assert state, end every screen test with the audit line, and keep
`test/a11y/app-contrast.test.ts` green.
7. **Run the checks** from the repo root, fix every `FAIL` line (each names the file, the rule and
the fix) and rerun until both print `RESULT: PASS`:
- `node ${CLAUDE_SKILL_DIR}/scripts/check-a11y-code.mjs .`
- `node ${CLAUDE_SKILL_DIR}/scripts/check-contrast.mjs .`
A contrast failure is fixed in the colours or taken to the developer (rule 12), never silenced.
8. **Large text on devices.** Copy `templates/.maestro/flows/a11y/01-large-text-core-screens.yaml`
to `.maestro/flows/a11y/`, replace its testIDs with the app's, and run it at the largest size on a
named iOS simulator and a named Android emulator (e2e-maestro's runner does it per locale; the
commands by hand are in the testing reference). Read every
screenshot for clipping, overlap and rows that should have stacked; reset the size afterwards.
9. **Before a release (human step).** Ask the developer to run the screen-reader checklist in the
testing reference on the release candidate, on both platforms; turn every reported problem into
a failing test first.
## Definition of doneFrom zakariaf/ReactNative-Skills, MIT.
More from zakariaf/ReactNative-Skills
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
ci-pipeline-and-gates
zakariaf
Builds GitHub Actions CI for Expo apps - verify on every PR, SHA-pinned actions, pinned runners and Node, device lanes, manual EAS releases.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
Signals
- Stars
- 11
- Forks
- 2
- Open issues
- 0
- Last commit (this folder)
- Oct 17, 2026 (11 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9
Cite this page
accessibility-as-code skill. Agent Mart. https://agentmart.dev/skills/zakariaf-accessibility-as-code (last updated 2026-10-24)