/validating-accessibility
Use this skill when creating, modifying, or reviewing any .tsx component in apps/web, even if the user doesn't mention "accessibility." Covers semantic HTML, aria labels, navigation landmarks, forms, dialogs, and keyboard navigation. Trigger on: adding buttons, links, toggles,
Claude Code
$ npx -y skills add tambo-ai/tambo --skill validating-accessibility --agent claude-code
Codex
$ npx skills add tambo-ai/tambo --skill validating-accessibility -a codex -y
Cursor
$ npx skills add tambo-ai/tambo --skill validating-accessibility -a cursor -y
Gemini CLI
$ npx skills add tambo-ai/tambo --skill validating-accessibility -a gemini-cli -y
GitHub Copilot
$ npx skills add tambo-ai/tambo --skill validating-accessibility -a github-copilot -y
OpenCode
$ npx skills add tambo-ai/tambo --skill validating-accessibility -a opencode -y
Windsurf
$ npx skills add tambo-ai/tambo --skill validating-accessibility -a windsurf -y
SKILL.md
validating-accessibility.SKILL.mdname: validating-accessibility
description: >-
Use this skill when creating, modifying, or reviewing any .tsx component in apps/web, even if
the user doesn't mention "accessibility." Covers semantic HTML, aria labels, navigation landmarks,
forms, dialogs, and keyboard navigation. Trigger on: adding buttons, links, toggles, icons, or
any interactive element; building or editing forms; adding dialogs or modals; reviewing UI code.
Includes inline verification patterns for scanning violations. Not for styling or layout changes
that don't involve interactive elements.
metadata:
internal: true
Accessibility Checklist
Every UI component in `apps/web` must meet these standards. No partial compliance.
Gotchas
- **`role="button"` divs may exist in the codebase** -- fix them when touching affected files. `<TableHead>` elements with `role="button"` for sortable columns are acceptable.
- **Nested interactive elements** -- when replacing a `<div role="button">` that contains a child `<button>` (e.g., a copy button inside a collapsible toggle), do not just swap the outer div to `<button>`. That creates invalid nested buttons. Instead, restructure into sibling elements: a toggle `<button>` and a separate action `<button>` side by side in a flex container.
- **Standalone inputs outside react-hook-form need manual ID pairing** -- use `useId()` with `htmlFor`/`id`. The shadcn `<FormField>` handles this automatically, but raw `<Input>` does not.
- **AlertDialog vs Dialog** -- use `AlertDialog` for destructive confirmations (requires `AlertDialogTitle` + `AlertDialogDescription`). Use `Dialog` for content/forms. Never build custom modal overlays.
- **Icon-only buttons without `aria-label`** are common in new code. Every icon-only button needs one, and it must include context: `Delete API key ${keyName}`, not just "Delete".
Semantic HTML
Use native elements. Never recreate `<button>` behavior with `<div role="button">` + keyboard handlers.
| Interaction | Element | | ---------------- | ------------------------------------------------------ | | Clickable action | `<button>` or `<Button>` from `@/components/ui/button` | | Navigation link | `<Link>` (Next.js) or `<a>` | | Navigation group | `<nav>` with descriptive `aria-label` | | Item list | `<ul>`/`<ol>` + `<li>` | | Section heading | `<h1>`-`<h6>` in order, never skip levels |
Aria Labels
Every interactive element without visible text needs `aria-label` with both action AND target:
<Button size="icon" aria-label={`Delete API key ${keyName}`}>
<Trash2 className="h-4 w-4" />
</Button>
<Switch aria-label={`${enabled ? "Disable" : "Enable"} skill ${skillName}`} />Prefer state-aware labels ("Copied!" vs "Copy"). Buttons with visible text skip `aria-label`.
**Reference implementations:** `copy-button.tsx` (state-aware), `context-attachment-badge.tsx` (contextual remove), `thread-table-header.tsx` (sort state) -- all in `apps/web/components/`.
Navigation Landmarks
Wrap navigation groups in `<nav>` with a unique `aria-label` per region on the page.
Forms
Use shadcn Form components from `@/components/ui/form` (`FormField`, `FormItem`, `FormLabel`, `FormControl`, `FormMessage`). They handle ID generation, label association, `aria-describedby`, and `aria-invalid` automatically.
For standalone inputs outside react-hook-form, pair `useId()` with `htmlFor`/`id`. Never use placeholder as label substitute.
Keyboard Navigation
- Only `tabIndex={0}` or `tabIndex={-1}` (never positive values)
- Never remove focus outlines
- Prefer `<button>` over manual Enter/Space handlers
Verification
Scan `apps/web/components` for common violations. For each check, grep for the pattern and fix any matches found.
Check 1: `role="button"` on non-button elements
Search for `role="button"` in `.tsx` files. Flag `<div` or `<span` elements with this attribute; they should be `<button>` or `<Button>` instead. `<TableHead>` elements with `role="button"` for sortable columns are acceptable.
**Pattern:** `role="button"`
Check 2: `<div onClick>` patterns
Search for `<div` elements with `onClick` handlers. These should use `<button>` instead for proper keyboard support.
**Pattern:** `<div[^>]*onClick`
Check 3: Positive tabIndex values
Search for `tabIndex` with values greater than 0. Only `tabIndex={0}` and `tabIndex={-1}` are allowed.
**Pattern:** `tabIndex={[1-9]`
Check 4: Icon buttons missing aria-label
Search for `size="icon"` in `.tsx` files. For each match, check surrounding lines (5-10 above and below) for `aria-label` on the same `<Button>` element or an `sr-only` span. Flag buttons that have neither.
**Pattern:** `size="icon"` without nearby `aria-label`
Manual checks
These cannot be detected by pattern matching:
- [ ] Form inputs have associated `<label>` elements
- [ ] Navigation groups use `<nav>` with unique `aria-label`
- [ ] Dialogs use Radix-based components (AlertDialog or Dialog)
- [ ] Focus outlines intact
Read more
name: validating-accessibility description: >- Use this skill when creating, modifying, or reviewing any .tsx component in apps/web, even if the user doesn't mention "accessibility." Covers semantic HTML, aria labels, navigation landmarks, forms, dialogs, and keyboard navigation. Trigger on: adding buttons, links, toggles, icons, or any interactive element; building or editing forms; adding dialogs or modals; reviewing UI code. Includes inline verification patterns for scanning violations. Not for styling or layout changes that don't involve interactive elements. metadata: internal: true
Accessibility Checklist
Every UI component in `apps/web` must meet these standards. No partial compliance.
Gotchas
- **`role="button"` divs may exist in the codebase** -- fix them when touching affected files. `<TableHead>` elements with `role="button"` for sortable columns are acceptable.
- **Nested interactive elements** -- when replacing a `<div role="button">` that contains a child `<button>` (e.g., a copy button inside a collapsible toggle), do not just swap the outer div to `<button>`. That creates invalid nested buttons. Instead, restructure into sibling elements: a toggle `<button>` and a separate action `<button>` side by side in a flex container.
- **Standalone inputs outside react-hook-form need manual ID pairing** -- use `useId()` with `htmlFor`/`id`. The shadcn `<FormField>` handles this automatically, but raw `<Input>` does not.
- **AlertDialog vs Dialog** -- use `AlertDialog` for destructive confirmations (requires `AlertDialogTitle` + `AlertDialogDescription`). Use `Dialog` for content/forms. Never build custom modal overlays.
- **Icon-only buttons without `aria-label`** are common in new code. Every icon-only button needs one, and it must include context: `Delete API key ${keyName}`, not just "Delete".
Semantic HTML
Use native elements. Never recreate `<button>` behavior with `<div role="button">` + keyboard handlers.
| Interaction | Element | | ---------------- | ------------------------------------------------------ | | Clickable action | `<button>` or `<Button>` from `@/components/ui/button` | | Navigation link | `<Link>` (Next.js) or `<a>` | | Navigation group | `<nav>` with descriptive `aria-label` | | Item list | `<ul>`/`<ol>` + `<li>` | | Section heading | `<h1>`-`<h6>` in order, never skip levels |
Aria Labels
Every interactive element without visible text needs `aria-label` with both action AND target:
<Button size="icon" aria-label={`Delete API key ${keyName}`}>
<Trash2 className="h-4 w-4" />
</Button>
<Switch aria-label={`${enabled ? "Disable" : "Enable"} skill ${skillName}`} />Prefer state-aware labels ("Copied!" vs "Copy"). Buttons with visible text skip `aria-label`.
**Reference implementations:** `copy-button.tsx` (state-aware), `context-attachment-badge.tsx` (contextual remove), `thread-table-header.tsx` (sort state) -- all in `apps/web/components/`.
Navigation Landmarks
Wrap navigation groups in `<nav>` with a unique `aria-label` per region on the page.
Forms
Use shadcn Form components from `@/components/ui/form` (`FormField`, `FormItem`, `FormLabel`, `FormControl`, `FormMessage`). They handle ID generation, label association, `aria-describedby`, and `aria-invalid` automatically.
For standalone inputs outside react-hook-form, pair `useId()` with `htmlFor`/`id`. Never use placeholder as label substitute.
Keyboard Navigation
- Only `tabIndex={0}` or `tabIndex={-1}` (never positive values)
- Never remove focus outlines
- Prefer `<button>` over manual Enter/Space handlers
Verification
Scan `apps/web/components` for common violations. For each check, grep for the pattern and fix any matches found.
Check 1: `role="button"` on non-button elements
Search for `role="button"` in `.tsx` files. Flag `<div` or `<span` elements with this attribute; they should be `<button>` or `<Button>` instead. `<TableHead>` elements with `role="button"` for sortable columns are acceptable.
**Pattern:** `role="button"`
Check 2: `<div onClick>` patterns
Search for `<div` elements with `onClick` handlers. These should use `<button>` instead for proper keyboard support.
**Pattern:** `<div[^>]*onClick`
Check 3: Positive tabIndex values
Search for `tabIndex` with values greater than 0. Only `tabIndex={0}` and `tabIndex={-1}` are allowed.
**Pattern:** `tabIndex={[1-9]`
Check 4: Icon buttons missing aria-label
Search for `size="icon"` in `.tsx` files. For each match, check surrounding lines (5-10 above and below) for `aria-label` on the same `<Button>` element or an `sr-only` span. Flag buttons that have neither.
**Pattern:** `size="icon"` without nearby `aria-label`
Manual checks
These cannot be detected by pattern matching:
- [ ] Form inputs have associated `<label>` elements
- [ ] Navigation groups use `<nav>` with unique `aria-label`
- [ ] Dialogs use Radix-based components (AlertDialog or Dialog)
- [ ] Focus outlines intact
Other skills on tambo.
More skills from other plugins.
migrate-radix-to-base
Migrates React projects and components from Radix UI to Base UI. Use when asked to migrate from radix, move to base-ui, convert…
taste-skill
tasteskill v2, the anti-slop frontend skill. Art direction for websites, distilled from a study of 247 curated gallery sites (One…
Questions about validating-accessibility.
- Which coding agents can use validating-accessibility?
- validating-accessibility installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

