Accessible components
Production-grade SaaS UI skills for AI coding agents (React + Tailwind + shadcn/ui) — Codex, Claude Code, Cursor, OpenCode. One npx command to install.
npx -y skills add param087/saas-ui-skills --skill accessible-componentsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when building or reviewing interactive UI that must meet WCAG 2.2 AA — focus management, ARIA roles and states, keyboard navigation, labels, and visible focus. Covers the accessibility mistakes agents make most.
SKILL.md
3.4 KB, as published. Nobody here has run it
Accessible Components
Overview
Accessibility is not a feature you add later — it's how you build interactive elements from the start. The single best move in a SaaS app is to use real semantic elements and Radix/shadcn primitives, which ship correct ARIA and keyboard behavior. Most a11y bugs come from re-inventing a button or dropdown with a <div>.
When to use
- Building any interactive control (menus, dialogs, tabs, toggles).
- Reviewing UI for keyboard and screen-reader support.
- A
<div onClick>is standing in for a button.
Rule 1: Use the right element
// ✗ Inaccessible: no focus, no Enter/Space, no role
<div onClick={save} className="btn">Save</div>
// ✓ Accessible for free
<button type="button" onClick={save} className="btn">Save</button>
A native <button> is focusable, fires on Enter/Space, and announces as a button. Same for <a> (navigation), <label> (form association), <nav>/<main>/<header> (landmarks).
Rule 2: Every input has a label
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" />
// Icon-only buttons need an accessible name:
<Button size="icon" aria-label="Close">
<X className="h-4 w-4" aria-hidden />
</Button>
Decorative icons get aria-hidden; the control gets aria-label.
Rule 3: Visible focus, always
Never outline-none without a replacement. Use a focus ring on every interactive element:
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
:focus-visible shows the ring for keyboard users without flashing it on mouse clicks.
Rule 4: Keyboard support
- All actions reachable via Tab; logical order (don't fight the DOM with
tabindex> 0). Esccloses overlays;Enter/Spaceactivate; arrow keys move within menus/tabs/radios.- Radix/shadcn primitives (
DropdownMenu,Dialog,Tabs,Select) implement these — prefer them over hand-rolled widgets.
Rule 5: Announce dynamic changes
Screen readers miss silent DOM updates. Use live regions for async status:
<p role="status" aria-live="polite">{saving ? "Saving…" : "All changes saved"}</p>
Toasts should render in an aria-live region (sonner/shadcn handle this — see notifications-and-toasts).
Quick checklist
- Color contrast ≥ 4.5:1 for text (3:1 for large/UI).
- Every control reachable and operable by keyboard.
- Visible focus indicator on each control.
- Inputs have associated labels; icon buttons have
aria-label. - State conveyed beyond color alone (icon/text too).
- Images have
alt; decorative onesalt=""/aria-hidden.
Pitfalls
<div onClick>instead of<button>— no keyboard, no role.outline-nonewith no focus replacement — keyboard users get lost.- Placeholder as label — disappears on input, fails screen readers.
- Color-only state (red border, no message) fails colorblind users.
- Hand-rolled dropdowns/modals missing focus trap and arrow keys — use primitives.
Hand-off
The accessibility baseline every other skill assumes — forms-and-validation, modals-and-dialogs, and navigation-patterns all build on these rules.