accessibility-checklist
Accessibility review checklist for React/Next.js components built on Radix UI / shadcn/ui. Covers component library misuse, form accessibility, accessible names, keyboard interaction, focus management, and dynamic content. Loaded by pr-review-frontend.
Install
mkdir -p .claude/skills/accessibility-checklist && curl -L -o skill.zip "https://mcp.directory/api/skills/download/4859" && unzip -o skill.zip -d .claude/skills/accessibility-checklist && rm skill.zipInstalls to .claude/skills/accessibility-checklist
About this skill
Accessibility Review Checklist
How to Use This Checklist
- Review changed components against the relevant sections below
- Not every section applies to every component — form checks only apply to form components, modal checks only apply to modals, etc.
- This codebase uses Radix UI / shadcn/ui extensively. These libraries handle most a11y patterns (keyboard nav, focus management, ARIA) automatically. Your primary job is to catch misuse of the library, not absence of manual implementation.
- When unsure whether a component library handles a pattern, lower confidence rather than asserting
§1 Component Library Misuse (Radix / shadcn/ui)
This is the highest-signal section for this codebase. Radix handles a11y correctly when used correctly — bugs come from misuse.
-
Dialog/Sheet without title: Radix
DialogandSheetrequireDialogTitle/SheetTitlefor screen reader announcement. IfDialogTitleis omitted or visually hidden withoutaria-labelonDialogContent, screen readers announce an unlabeled dialog.- Common violation:
<DialogContent>with no<DialogTitle>and noaria-label - Note: Using
<VisuallyHidden><DialogTitle>...</DialogTitle></VisuallyHidden>is a valid pattern for dialogs where a visible title doesn't fit the design
- Common violation:
-
AlertDialog without description:
AlertDialogContentshould includeAlertDialogDescriptionfor screen readers to understand the confirmation context. If omitted, addaria-describedby={undefined}to explicitly opt out (otherwise Radix warns). -
Select/Combobox without accessible trigger label: Radix
Selectneedsaria-labelon the trigger when there's no visible label. Customgeneric-select.tsxandgeneric-combo-box.tsxwrappers should propagate labels.- Common violation:
<Select>inside a form field that has a visual label, but the label isn't associated viahtmlForor wrapping
- Common violation:
-
DropdownMenu items without accessible names: Icon-only menu items need text content or
aria-label. Menu items that are just icons (e.g., copy, delete, edit) need text.- Correct pattern:
<DropdownMenuItem><TrashIcon /> Delete</DropdownMenuItem>(icon + text) - Violation:
<DropdownMenuItem><TrashIcon /></DropdownMenuItem>(icon only, no text, no aria-label)
- Correct pattern:
-
Tooltip as only accessible name: Tooltip text is not reliably announced by all screen readers. If a control's only accessible name is in a tooltip, it needs
aria-labelas well.- Common pattern to flag:
<Tooltip><TooltipTrigger><Button><Icon /></Button></TooltipTrigger><TooltipContent>Delete</TooltipContent></Tooltip>— Button needsaria-label="Delete"
- Common pattern to flag:
-
Overriding Radix's keyboard handling: If a component wraps a Radix primitive and adds
onKeyDownthat callse.preventDefault()ore.stopPropagation(), it may break Radix's built-in keyboard navigation.
§2 Forms & Labels
The codebase uses react-hook-form + Zod with shadcn/ui's Form component, which auto-associates labels via FormItem context. Issues arise when forms bypass this pattern.
-
Every form input must have an accessible name: Via
<FormLabel>,<label htmlFor={id}>,aria-label, oraria-labelledby. Placeholder text alone is NOT a label.- Common violation: Custom inputs outside
<FormField>/<FormItem>that don't get auto-association - Common violation:
<Input placeholder="Enter name" />used standalone without any label
- Common violation: Custom inputs outside
-
Error messages must be associated with their input: shadcn/ui's
<FormMessage>auto-associates viaaria-describedbywhen inside<FormItem>. Custom error rendering outside this pattern loses the association.- Flag: Error text rendered near an input but not using
<FormMessage>or manualaria-describedby
- Flag: Error text rendered near an input but not using
-
Required fields must be indicated programmatically: Use
aria-required="true"or nativerequired, not just a visual asterisk. TheFormcomponent doesn't add this automatically — it comes from the Zod schema validation at submit time, not at the HTML level. -
Grouped controls need group semantics: Radio groups and checkbox groups should use
<RadioGroup>(Radix) or<fieldset>/<legend>. Loose radio buttons or checkboxes without group context confuse screen readers.- Scope: Configuration pages, settings forms, multi-option selectors
§3 Accessible Names (Icons & Buttons)
With 48 shadcn/ui components and heavy icon usage (Lucide React), icon-only interactive elements are a primary risk area.
-
Icon-only buttons must have
aria-label: Buttons containing only an icon (no visible text) needaria-labeldescribing the action.- Common violation:
<Button variant="ghost" size="icon"><TrashIcon /></Button>withoutaria-label - Very common in: data tables (row actions), toolbars, card headers, dialog close buttons
- Note: shadcn/ui's
Dialogclose button already includes<span className="sr-only">Close</span>— don't flag this
- Common violation:
-
Icon-only links need accessible names: Same as buttons —
<a>or<Link>with only an icon needsaria-label. -
sr-onlytext is a valid alternative toaria-label:<Button><TrashIcon /><span className="sr-only">Delete item</span></Button>is correct. Don't flag this pattern as missing a label. -
Decorative icons should be hidden: Icons that are purely decorative (next to visible text) should have
aria-hidden="true"to avoid redundant announcements.- Correct:
<Button><PlusIcon aria-hidden="true" /> Add item</Button> - Also correct: Lucide icons may set
aria-hiddenby default — check before flagging
- Correct:
§4 Semantic HTML & Regression Guard
The codebase currently has no <div onClick> anti-patterns. This section guards against regressions.
-
Interactive elements must use native interactive HTML:
<button>for actions,<a>/<Link>for navigation. NOT<div>,<span>, or<p>withonClick.- Flag any new
<div onClick>or<span onClick>in the diff as CRITICAL - Exception: Components from Radix that render proper elements under the hood are fine
- Flag any new
-
Tables must use semantic HTML:
<table>,<thead>,<tbody>,<th>,<td>. The codebase already does this. Flag any new data display that should be a table but uses<div>grid instead.- Consider:
<th>elements should havescope="col"orscope="row"for complex tables
- Consider:
-
Don't disable zoom: Flag
user-scalable=noormaximum-scale=1in viewport meta tags.
§5 Focus Management
Radix Dialog handles focus trap and restore automatically. This section covers what Radix doesn't handle.
-
Custom modals/overlays must manage focus: Any modal-like UI NOT built on Radix Dialog (e.g., custom overlays, fullscreen panels, React Flow side panels) must:
- Move focus into the overlay when it opens
- Trap focus while open (Tab cycles within the overlay)
- Return focus to the trigger when closed
- Close on Escape
-
Focus visible indicator must not be removed:
outline-none/outline: nonewithout afocus-visible:ring-*replacement removes the only visual cue for keyboard users.- Note: The codebase consistently uses
focus-visible:ring-*alongsideoutline-none— this is correct. Only flag if a new component usesoutline-nonewithout the replacement.
- Note: The codebase consistently uses
-
Route change focus (Next.js App Router): After client-side navigation, focus should move to the main content. Next.js App Router may handle this — only flag if a custom route change mechanism bypasses the framework's handling.
-
Positive tabIndex is an anti-pattern:
tabIndex={0}andtabIndex={-1}are fine.tabIndex={1}or higher overrides natural order and creates unpredictable navigation. Flag any positive tabIndex values.
§6 Dynamic Content & Live Regions
With 287 toast usages (Sonner) and chat streaming interfaces, announcements for screen readers matter.
-
Sonner toasts: Sonner uses
role="status"witharia-live="polite"by default. This is correct. Only flag if:- A custom toast/notification bypasses Sonner and doesn't use a live region
- An error toast should use
role="alert"(assertive) instead ofrole="status"(polite) for critical errors
-
Loading states should be communicated: Skeleton loaders and spinners should be accompanied by screen reader announcements. Options:
aria-busy="true"on the loading container<span className="sr-only">Loading...</span>inside the spinneraria-live="polite"region that announces "Loading..." then announces when content is ready- Note: The codebase's
Spinnercomponent already hasaria-label— check that new loading patterns follow suit
-
Chat streaming messages: For the copilot/playground chat interfaces, new messages should be announced to screen readers. The
@inkeep/agents-uilibrary should handle this — only flag if custom chat rendering bypasses the library's announcements. -
Inline form validation: When validation errors appear dynamically (without page reload), they should either:
- Be associated with the input via
aria-describedby(shadcn/ui's<FormMessage>does this) - Or use
aria-live="polite"to announce the error - Only flag custom validation rendering outside the
<FormMessage>pattern
- Be associated with the input via
§7 Specialized Components
These components have unique a11y considerations beyond standard patterns.
-
Monaco Editor: Has known a11y limitations for screen reader users. When Monaco is used for required input (not just optional code editing), consider providing an alternative text input fallback. Flag only if a new Monaco instance is introduced without consideration.
-
React Flow (node graph editor): Keyboard navigation in visual node editors is inherently difficult. When React Flow is used:
- Ensure all node operations are also accessible via context menus or keyboard shortcuts
- Node labels should be readable by screen readers
- Flag only if new React Flow interactions are added without keyboard
Content truncated.
More by inkeep
View all skills by inkeep →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversCreate modern React UI components instantly with Magic AI Agent. Integrates with top IDEs for fast, stunning design and
Enhance software testing with Playwright MCP: Fast, reliable browser automation, an innovative alternative to Selenium s
Use Chrome DevTools for web site test speed, debugging, and performance analysis. The essential chrome developer tools f
Optimize your codebase for AI with Repomix—transform, compress, and secure repos for easier analysis with modern AI tool
Mobile Next offers fast, seamless mobile automation for iOS and Android. Automate apps, extract data, and simplify mobil
Vizro creates and validates data-visualization dashboards from natural language, auto-generating chart code and interact
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.