Skip to content
Agentmart
Submit a tool

accessibility-as-code

Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.

Skill by zakariaf

IndexedMIT★ 11
Write a review

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.

We use your email only for this newsletter. Privacy

About

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).

No 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 done

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

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
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

accessibility-as-code skill. Agent Mart. https://agentmart.dev/skills/zakariaf-accessibility-as-code (last updated 2026-10-24)