file-tech-debt
Files a new tech-debt GitHub issue for an out-of-scope code-review finding. Builds the dedup key, skips an open or…
Resolves axe-core accessibility violations reported by a story under Vitest (addon-a11y), Playwright (.playwright/a11y.ts), or the code-audit-frontend agent's a11y bucket, with fix patterns for the common violations and a fix-then-verify loop for the rest. Trigger on any axe
Claude Code
$ npx -y skills add gaia-harness/gaia --skill a11y-fixes --agent claude-code
Codex
$ npx skills add gaia-harness/gaia --skill a11y-fixes -a codex -y
Cursor
$ npx skills add gaia-harness/gaia --skill a11y-fixes -a cursor -y
Gemini CLI
$ npx skills add gaia-harness/gaia --skill a11y-fixes -a gemini-cli -y
GitHub Copilot
$ npx skills add gaia-harness/gaia --skill a11y-fixes -a github-copilot -y
OpenCode
$ npx skills add gaia-harness/gaia --skill a11y-fixes -a opencode -y
Windsurf
$ npx skills add gaia-harness/gaia --skill a11y-fixes -a windsurf -y
name: a11y-fixes description: Resolves axe-core accessibility violations reported by a story under Vitest (addon-a11y), Playwright (.playwright/a11y.ts), or the code-audit-frontend agent's a11y bucket, with fix patterns for the common violations and a fix-then-verify loop for the rest. Trigger on any axe rule id appearing in test output. model: haiku
Contents: Fix-then-verify loop; color-contrast; label; label-title-only; image-alt; button-name; link-name; region / landmark-one-main; heading-order; aria-allowed-attr; aria-required-attr; aria-required-children / aria-required-parent; aria-valid-attr-value; focus-trap (focus management); tabindex; html-has-lang; document-title; duplicate-id; listitem; definition-list; Upstream shadcn and Base UI findings.
Violations come from addon-a11y, which axe-checks every story under Vitest (configured in `.storybook/a11y.ts`), `.playwright/a11y.ts` (Playwright), or the `code-audit-frontend` agent's a11y bucket. General a11y guidance lives in `apps/web/.claude/rules/accessibility.md`.
1. Fix one violation with the pattern below; for a rule id not listed here, use the violation's `help` and `failureSummary` from the axe output. 2. Re-run the reporter that flagged it: Vitest `pnpm test --run --project storybook <story-file>` (`--project storybook-dark` for a dark-theme violation), Playwright `pnpm pw <spec-file>`. 3. Some rules are not checked by the story run. addon-a11y runs the WCAG 2.0/2.1 A and AA tags and disables `region` (a story renders a fragment outside the page landmarks), so `landmark-one-main` and `page-has-heading-one` never fire there: assert the page landmark and heading in a `.test()` on the page story instead (exactly one `main`, one level-1 heading; `wiki/decisions/Stories as Tests.md`). 4. Repeat until the reporter shows no violations.
WCAG AA requires 4.5:1 for normal text, 3:1 for large text. Use the theme role tokens (see `apps/web/.claude/rules/tailwind.md`) instead of raw palette colors: each token carries a light and a dark value that pass in both themes. Pair a surface token with its foreground (`bg-primary` with `text-primary-foreground`).
// BAD, fails contrast in dark mode, raw colors <p className="text-gray-400 bg-white">Status</p> // GOOD, role tokens, contrast-safe in both themes <p className="bg-background text-foreground">Status</p>
If a token itself fails (the placeholder values are a neutral default), fix the value in `app/styles/theme.css` and keep it an accessibility fix, per `apps/web/.claude/rules/design-baseline.md`.
Form inputs need an associated label. Put the control in `ConformField` rather than a bare `<label>`: it links the label and the errors. See `apps/web/.claude/skills/react-code/references/conform-forms.md` for each control type.
// BAD, bare input with no label association
<input type="text" name="email" />
// GOOD, ConformField wires the label to the ui control
<ConformField field={fields.email} label={t('email')}>
{(controlOptions) => (
<Input {...getInputProps(fields.email, {...controlOptions, type: 'email'})} />
)}
</ConformField>`title` attributes are not labels, screen readers and mobile devices ignore them. Use `aria-label` or a real `<label>`.
// BAD, title is not an accessible name
<input type="text" title="Search" />
// GOOD, aria-label provides the accessible name
<input type="text" aria-label={t('search')} />Every `<img>` needs `alt`. Content images describe the image; decorative images use `alt=""`. See `apps/web/.claude/rules/accessibility.md`.
// BAD, no alt attribute
<img src="/logo.png" />
// GOOD, content image
<img src="/logo.png" alt={t('companyLogo')} />
// GOOD, decorative image, hidden from AT
<img src="/divider.svg" alt="" />Buttons need an accessible name. Visible text is fine; icon-only buttons need `aria-label`.
// BAD, icon-only button with no name
<button onClick={onClose}>
<CloseIcon />
</button>
// GOOD, aria-label supplies the name
<button aria-label={t('close')} onClick={onClose}>
<CloseIcon />
</button>
// GOOD, visible text, no aria-label needed
<button onClick={onSave}>{t('save')}</button>Same pattern as `button-name`, anchors need an accessible name.
// BAD, icon-only link
<Link to="/settings"><GearIcon /></Link>
// GOOD, aria-label on the link
<Link to="/settings" aria-label={t('settings')}>
<GearIcon />
</Link>Page content must live inside a landmark, and there must be exactly one `<main>`. The Layout component owns the `<main>` landmark, page components render inside it and should not add their own.
// BAD, page component wraps itself in <main>, duplicating Layout's
const Page = () => (
<main>
<h1>{t('title')}</h1>
</main>
);
// GOOD, page renders into Layout's <main>
const Page = () => (
<>
<h1>{t('title')}</h1>
</>
);One `<h1>` per page. Levels do not skip, `h2 → h4` is a violation.
// BAD, skips h3
<h2>{t('section')}</h2>
<h4>{t('subsection')}</h4>
// GOOD, sequential levels
<h2>{t('section')}</h2>
<h3>{t('subsection')}</h3>ARIA attributes are role-scoped. Look up the role; only attrs in its allowed list are valid. Common case: `aria-checked` only on `role="checkbox"`, `role="radio"`, `role="menuitemcheckbox"`, `role="menuitemradio"`, `role="switch"`, `role="treeitem"`.
// BAD, aria-checked is not allowed on a button
<button aria-checked={selected}>{t('toggle')}</button>
// GOOD, aria-pressed for buttons, aria-checked for checkbox role
<button aria-pressed={selected}>{t('toggle')}</button>Some roles require companion attrs. `role="combobox"` requires `aria-expanded` and `aria-controls`. On a disclosure button axe does not require `aria-contro
Claude is raw power. GAIA is order and focus. A serious harness for Claude Code. It keeps Claude-shipped code production-grade as your team scales. The React frontend is handled. You build the rest of your app on top.
Files a new tech-debt GitHub issue for an out-of-scope code-review finding. Builds the dedup key, skips an open or…
Runs GAIA wiki maintenance (sync, consolidate, lint with fixes) in one command and lands it as one commit and PR. Trigger on…
Applies descriptive, self-documenting naming in any language (TypeScript, bash, workflow YAML, Dockerfiles). Use this skill…
Maintainer-only. Rewrites a version's GAIA CHANGELOG entries as plain-language public release notes for the marketing site…
Runs test-driven development with a red-green-refactor loop, an opt-in tool for logic where test-first helps. Use when the user…
Remediates dependencies in place of Dependabot version and security updates. Discovers outdated packages and open security…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.