@codecademy/gamut
68.6.068.6.1-alpha.bc8b32.0
agent-tools/rules/accessibility.mdc+
agent-tools/rules/accessibility.mdcNew file+78
Index: package/agent-tools/rules/accessibility.mdc
===================================================================
--- package/agent-tools/rules/accessibility.mdc
+++ package/agent-tools/rules/accessibility.mdc
@@ -0,0 +1,78 @@
+---
+description: Apply these guardrails when editing Gamut UI in TS/JS/TSX/JSX. Universal rules (always loaded). Form wiring depth: **`gamut-forms`** skill; other component matrix and audit detail: **`gamut-accessibility`** — those skills do not repeat this rule set.
+alwaysApply: true
+globs: ['*.tsx', '*.ts', '*.jsx', '*.js']
+---
+
+# Gamut Accessibility Rules
+
+## Prefer HTML over ARIA
+
+Unnecessary ARIA can cause harm. If a native HTML element or attribute with the semantics and behavior you need already exists, use it. Reach for ARIA only when native HTML is genuinely insufficient for the pattern.
+
+## A Role is a Promise
+
+ARIA roles modify the accessibility tree and _imply_ behavior. Always ensure that the implied keyboard behavior, focusability, and interactivity exists when a role is used.
+
+## ARIA can both cloak and enhance
+
+ARIA can augment native semantics (`aria-pressed` on a `<button>`) or override them entirely (`role="menuitem"` on an `<a>`). Both capabilities are powerful and dangerous. Override only when native HTML genuinely doesn't fit the pattern; when augmenting, don't contradict the native semantics.
+
+## Align accessible names with visible copy
+
+Prefer wiring names through visible text and native `<label>` / control text / `alt` over using `aria-label`. Point `aria-labelledby` at the visible heading or label that should define the name if it's not possible to name elements from their content. Use bare `aria-label` when there is no suitable visible label.
+
+## Treat missing visible labels as a design smell
+
+When there is no visible text for a nameable element, consider this a sign that the content design could be improved, but not a requirement that it is changed. This is not an accessibility violation.
+
+```html
+<!-- smell: this list has no conceptual name, so we have to create one using ARIA -->
+<ul aria-label="List heading">
+ <li>...</li>
+</ul>
+
+<!-- better: the list's name is visible and can be used for its accessible name -->
+<h2 id="list-name">List heading</h2>
+<ul aria-labelledby="list-name">
+ <li>...</li>
+</ul>
+```
+
+## Use Gamut primitives — do not fake buttons or dialogs
+
+- **Actions:** use Gamut button atoms (`FillButton`, `TextButton`, `StrokeButton`, `CTAButton`, `IconButton`) — not `<div onClick>`, not `<span role="button">`, not `<a>` without `href` for actions. Variant and `tip` guidance: [`guidelines/components/buttons.md`](../guidelines/components/buttons.md).
+- **Tabs / overlays:** `Tabs`, `Dialog`, `Modal`, and related primitives implement keyboard and focus patterns in code — still supply labels, titles, and trigger semantics as documented in the skill.
+
+## Every interactive control needs an accessible name
+
+- **`IconButton`** — provide `tip` (accessible name for icon-only).
+- **`InfoTip`** — provide `ariaLabel` or `ariaLabelledby`; there is no automatic fallback.
+- Decorative icon SVGs next to visible text — `aria-hidden="true"` on the icon.
+
+## Form label association
+
+Match **`htmlFor`** on **`<FormGroupLabel>`** with the **`id`** on the control. Base **`<FormGroup>`** renders live regions for **`error`** and **`description`**; **`GridForm`** and **`ConnectedForm`** add field wiring (**`aria-describedby`**, **`aria-invalid`**, first-error **`aria-live`** behavior) — do not add redundant duplicate regions. Depth: **[`skills/gamut-forms/SKILL.md`](../skills/gamut-forms/SKILL.md)**.
+
+## Screen-reader-only text
+
+Use `<Text screenreader>` for visually hidden but announced content. `<HiddenText>` is deprecated.
+
+## Color and contrast
+
+Do not hardcode hex for adaptive UI. Prefer semantic tokens and **`ColorMode` / `<Background>`** so surfaces track theme and mode — see the **`gamut-color-mode`** skill and [`foundations/modes.md`](../guidelines/foundations/modes.md). Default pairings support accessible UI, but **tokens do not guarantee WCAG compliance** for every layout; validate non-standard combinations.
+
+## Focus visibility
+
+Never suppress focus indicators with `outline: none` or `outline: 0` without a visible replacement. Gamut’s focus styles are intentional (WCAG 2.4.7).
+
+## Where to read more (minimal index)
+
+| Topic | Primary doc |
+| --- | --- |
+| Forms (`GridForm`, `ConnectedForm`, `FormGroup`, validation, live regions) | [`skills/gamut-forms/SKILL.md`](../skills/gamut-forms/SKILL.md) |
+| Component matrix (tips, overlays, composites, checklists; not form wiring) | [`skills/gamut-accessibility/SKILL.md`](../skills/gamut-accessibility/SKILL.md) |
+| Button variants, `IconButton` `tip`, `disabled` vs `aria-disabled` | [`guidelines/components/buttons.md`](../guidelines/components/buttons.md) |
+| ColorMode, `Background`, semantic color roles | **`gamut-color-mode`** skill · [`foundations/modes.md`](../guidelines/foundations/modes.md) · [`foundations/color.md`](../guidelines/foundations/color.md) |
+| Tokens, `css` / `variant` / `states` | Storybook [Meta / Best practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page) |
+| Install, `GamutProvider`, CSP | Storybook [Meta / Installation](https://gamut.codecademy.com/?path=/docs-meta-installation--page) |