eslint-fixes
Resolve specific ESLint errors and warnings that appear in this project. Use when fixing lint failures, ESLint reported issues, or autofix conflicts (e.g.…
Resolve axe-core accessibility violations reported by Vitest (test/a11y.ts), Playwright (.playwright/a11y.ts), or the code-audit-frontend agent's a11y bucket. Trigger on any axe rule id appearing in test output, not only the ones named here. Contains fix patterns for the most
$ npx -y skills add gaia-react/gaia --skill a11y-fixes --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/a11y-fixesContext preview
The summary Claude sees to decide when to auto-load this skill.
Resolve axe-core accessibility violations reported by Vitest (test/a11y.ts), Playwright (.playwright/a11y.ts), or the code-audit-frontend agent's a11y bucket. Trigger on any axe rule id appearing in test output, not only the ones named here. Contains fix patterns for the most
name: a11y-fixes description: Resolve axe-core accessibility violations reported by Vitest (test/a11y.ts), Playwright (.playwright/a11y.ts), or the code-audit-frontend agent's a11y bucket. Trigger on any axe rule id appearing in test output, not only the ones named here. Contains fix patterns for the most common violations (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, tabindex, html-has-lang, document-title, duplicate-id, listitem, definition-list); for any violation not listed, apply the general axe guidance and the same fix-then-verify loop. model: haiku
How to resolve specific axe-core violations in this project.
Violations come from `test/a11y.ts` (Vitest), `.playwright/a11y.ts` (Playwright), or the `code-audit-frontend` agent's a11y bucket. General a11y guidance lives in `.claude/rules/accessibility.md`.
WCAG AA requires 4.5:1 for normal text, 3:1 for large text. Use the project's semantic Tailwind tokens (see `.claude/rules/tailwind.md`) instead of arbitrary palette colors, they pair light/dark modes correctly.
// BAD, fails contrast in dark mode, raw colors <p className="text-gray-400 bg-white">Status</p> // GOOD, semantic tokens, contrast-safe in both modes <p className="text-body bg-body">Status</p>
Form inputs need an associated `<label>`. Use GAIA's `Field` wrapper from `~/components/Form/Field` rather than a bare `<label>`, it wires `htmlFor`, error text, and description automatically. See the `form-components.md` audit extension.
// BAD, bare input with no label association
<input type="text" name="email" />
// GOOD, Field wraps a project input with the right wiring
<Field type="input" name="email" label={t('email')}>
<InputText name="email" />
</Field>`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 `.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>`. GAIA's 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. Disclosure widgets (`aria-expanded`) need `aria-controls` pointing at the controlled element's id.
// BAD, aria-expanded with no aria-controls
<button aria-expanded={open}>{t('menu')}</button>
// GOOD, aria-controls names the panel
<button aria-expanded={open} aria-controls="menu-panel">
{t('menu')}
</button>
<div id="menu-panel" hidden={!open}>...</div>Composite roles need their child roles, and child roles need the right parent. Prefer semantic HTML (`<ul><li>`, `<select><option>`) over recreating these structures with ARIA.
// BAD, role="listbox" with no role="option" children
<div role="listbox">
<div>{t('one')}</div>
<div>{t('two')}</div>
</div>
// GOOD, semantic <select> with <option> children
<select aria-label={t('choose')}>
<option value="1">{t('one')}</option>
<option value="2">{t('two')}</option>
</select>`aria-*` attrs that take id refs must point at existing ids; boolean attrs take `true`/`false`, not arbitrary strings.
// BAD, aria-labelledby points at id that does not exist
<input aria-labelledby="missing-id" />
// GOOD, id exists in the DOM
<>
<span id="email-label">{t('email')}</span>
<input aria-labelledby="email-label" />
</>Modals must trap foc
Claude is raw power. GAIA is order and focus. The foundation that keeps Claude-shipped code production-grade as your team scales. The React frontend is handled. You build the rest of your app on top. Every convention enforced in code.
Repo: gaia-react/gaia
Resolve specific ESLint errors and warnings that appear in this project. Use when fixing lint failures, ESLint reported issues, or autofix conflicts (e.g.…
File a new tech-debt GitHub issue for an out-of-scope code-review finding, building the dedup key, checking for an existing open or declined-closed match, and…
Generate a comprehensive GAIA session handoff document, accomplishments, decisions, current state, open questions, so context can be cleared or compacted…
Restore context from the most recent GAIA session handoff and suggest the next action. Trigger on `/gaia-pickup` or natural-language asks like "pick up where…
Diagnose React render performance by driving a micro-interaction, capturing real renders, and surfacing memo-defeating reference instability with a recommended…
GAIA wiki maintenance, sync, consolidate, lint. Runs the full chain with no sub-arg, or a single stage when named (sync | consolidate | lint); append --force…