Accessibility
A portable Claude Code skills library (plugin) for a Django 5.2 + Next.js 16 house stack: 30 model-agnostic, tenant-isolation-and-security-first skills.
npx -y skills add Deadlymind/nanolama --skill accessibilityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 21 days oldThe repository was created 21 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 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
Builds WCAG 2.2 AA accessible React UI on shadcn/Radix primitives — keyboard operability with a visible focus ring, dialog focus trap and restore, labelled form fields wired to aria-describedby errors, token-based color contrast, and Arabic RTL mirroring. Use when adding or reviewing an interactive component, an icon-only button, a dialog or menu, a form field's error wiring, or a keyboard/focus/contrast/screen-reader fix, and when making a screen work in RTL. Not for translation plumbing or bidi text setup (see i18n-rtl) or design-token definition (see tailwind-shadcn).
SKILL.md
5.7 KB, as published. Nobody here has run it
Accessibility (WCAG 2.2 AA on React + Radix)
When to use
Building or reviewing any interactive UI on this stack — a button, dialog, menu, form field, or a whole screen — that a keyboard or screen-reader user must be able to operate. Treat a control that only works with a mouse as a bug, not a polish item. Target WCAG 2.2 AA as the bar.
Pattern
Reach for an accessible primitive before you hand-build behavior. A Radix/shadcn
Button, Dialog, DropdownMenu, or Tabs ships the correct role, focus
management, and keyboard handling for free; a <div onClick> ships none of it and
you will re-implement them wrong. The invariant: everything is operable by keyboard
with a visible focus ring, every control has an accessible name, and state
(errors, expansion, selection) is exposed to assistive tech, not just painted.
Idioms
- Never
div-with-onClick for a control. A clickable div has no role, is not in the tab order, and ignores Enter/Space. Use the primitive (Button, a Radix trigger) so screen readers announce it and keyboards reach it. - Keyboard first, visibly. Every action reachable by Tab; never remove the
focus outline without replacing it. Style
:focus-visible(not:focus) so a mouse click does not flash a ring but a Tab does. - Trap and restore focus in overlays. A dialog/menu keeps Tab inside it while
open, closes on Escape, and returns focus to the element that opened it. Radix
Dialogdoes this — do not fight it with manualfocus()calls. - Name every control. Icon-only buttons need an
aria-label(or visually hidden text); an unlabelled icon button is silence to a screen reader. - Wire form errors. Associate each field with its
<label>, and pointaria-describedbyat the error node witharia-invalidon the input, so the error is announced when focus lands (tieszod-forms). - Contrast from tokens. Meet 4.5:1 body / 3:1 large-text and UI contrast using
semantic design tokens, not ad-hoc hex — one accessible palette, applied
everywhere (ties
tailwind-shadcn).
Example
// Icon-only button: a screen reader announces "Delete invoice", not "button".
<Button variant="ghost" size="icon" aria-label="Delete invoice">
<TrashIcon aria-hidden="true" /> {/* decorative: hidden from AT */}
</Button>
// Dialog: Radix traps focus while open, restores it to the trigger on close,
// and closes on Escape. Title/description are wired to the dialog by id.
<Dialog>
<DialogTrigger asChild>
<Button>Delete</Button>
</DialogTrigger>
<DialogContent> {/* focus moves in; Tab is trapped here */}
<DialogTitle>Delete this invoice?</DialogTitle>
<DialogDescription>This cannot be undone.</DialogDescription>
<DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>
</DialogContent>
</Dialog>
RTL
For Arabic, the whole layout mirrors. Use CSS logical properties
(margin-inline-start, padding-inline-end, inset-inline) instead of
left/right so spacing flips automatically, and let icons/arrows that imply
direction mirror too. Reading order and focus/Tab order must follow the visual
order in both directions — set dir="rtl" on the container and verify Tab walks
the mirrored layout sensibly (ties i18n-rtl).
Verify
Automated checkers catch only part of the picture — roughly what is machine-testable
(missing labels, contrast ratios, bad ARIA), not whether focus order makes sense or
a flow is usable. Flow- and layout-level WCAG 2.2 criteria (target size, focus not
obscured, redundant entry, consistent help, dragging alternatives) are baked in at
design time by product-ux-design and visual-ui-design; this audit verifies they
survived implementation. Run all three passes:
- an axe-style automated scan in CI on rendered pages,
- a keyboard-only pass (unplug the mouse: Tab, Enter, Space, Escape, arrows),
- a screen-reader pass (VoiceOver/NVDA) on the primary flow.
Adapt to your repo
Confirm which primitive library you actually use (Radix under shadcn here; Headless
UI and others give the same guarantees differently) and that its focus-trap default
is on. Point contrast checks at your own token names. Version-pin nothing from this
skill — confirm the current release of any axe-style checker or testing library
through version-check.
Gotchas
aria-labelon a<div>does nothing useful without a role — that is why the primitive matters; do not paper over a wrong element with ARIA.- Removing the focus outline for looks is a WCAG failure; replace it, never delete it.
placeholderis not a label — it vanishes on input and is often skipped by AT.display:noneandaria-hiddenremove content from the accessibility tree; a "visually hidden" utility (clipped, not hidden) is what keeps label text for AT.- RTL breaks when you hard-code
left/rightormargin-left; logical properties are what let one component serve both directions. - A passing axe scan is a floor, not a pass — it cannot judge focus order or whether a custom widget is actually operable.
See also
zod-formstailwind-shadcni18n-rtlnextjs-module