Skip to content
Development
Skill

/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,

From plugin
tambo
11k8 skills2 agents4 commands
Install
$ npx -y skills add tambo-ai/tambo --skill validating-accessibility --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/validating-accessibility

Context preview

The summary Claude sees to decide when to auto-load this skill.

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,

SKILL.md

validating-accessibility.SKILL.md
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
Read more
Ships withtambo

Generative UI SDK for React

Get the whole plugin

Other skills on tambo.