Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
AI-powered UI styling skill with 67 UI styles and 161 reasoning rules for professional interface design.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/shadcn-accessibility.md
1# shadcn/ui Accessibility Patterns23ARIA patterns, keyboard navigation, screen reader support, and accessible component usage.45## Foundation: Radix UI Primitives67shadcn/ui built on Radix UI primitives - unstyled, accessible components following WAI-ARIA design patterns.89Benefits:10- Keyboard navigation built-in11- Screen reader announcements12- Focus management13- ARIA attributes automatically applied14- Tested against accessibility standards1516## Keyboard Navigation1718### Focus Management1920**Focus visible states:**21```tsx22<Button className="focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">23Accessible Button24</Button>25```2627**Skip to content:**28```tsx29<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2">30Skip to content31</a>3233<main id="main-content">34{/* Content */}35</main>36```3738### Dialog/Modal Navigation3940Dialogs trap focus automatically via Radix Dialog primitive:4142```tsx43import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"4445<Dialog>46<DialogTrigger>Open</DialogTrigger>47<DialogContent>48{/* Focus trapped here */}49<input /> {/* Auto-focused */}50<Button>Action</Button>51{/* Esc to close, Tab to navigate */}52</DialogContent>53</Dialog>54```5556Features:57- Focus trapped within dialog58- Esc key closes59- Tab cycles through focusable elements60- Focus returns to trigger on close6162### Dropdown/Menu Navigation6364```tsx65import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"6667<DropdownMenu>68<DropdownMenuTrigger>Open</DropdownMenuTrigger>69<DropdownMenuContent>70<DropdownMenuItem>Profile</DropdownMenuItem>71<DropdownMenuItem>Settings</DropdownMenuItem>72<DropdownMenuItem>Logout</DropdownMenuItem>73</DropdownMenuContent>74</DropdownMenu>75```7677Keyboard shortcuts:78- `Space/Enter`: Open menu79- `Arrow Up/Down`: Navigate items80- `Esc`: Close menu81- `Tab`: Close and move focus8283### Command Palette Navigation8485```tsx86import { Command } from "@/components/ui/command"8788<Command>89<CommandInput placeholder="Search..." />90<CommandList>91<CommandGroup heading="Suggestions">92<CommandItem>Calendar</CommandItem>93<CommandItem>Search</CommandItem>94</CommandGroup>95</CommandList>96</Command>97```9899Features:100- Type to filter101- Arrow keys to navigate102- Enter to select103- Esc to close104105## Screen Reader Support106107### Semantic HTML108109Use proper HTML elements:110111```tsx112// Good: Semantic HTML113<button>Click me</button>114<nav><a href="/">Home</a></nav>115116// Avoid: Div soup117<div onClick={handler}>Click me</div>118```119120### ARIA Labels121122**Label interactive elements:**123```tsx124<Button aria-label="Close dialog">125<X className="h-4 w-4" />126</Button>127128<Input aria-label="Email address" type="email" />129```130131**Describe elements:**132```tsx133<Button aria-describedby="delete-description">134Delete Account135</Button>136<p id="delete-description" className="sr-only">137This action permanently deletes your account and cannot be undone138</p>139```140141### Screen Reader Only Text142143Use `sr-only` class for screen reader only content:144145```tsx146<Button>147<Trash className="h-4 w-4" />148<span className="sr-only">Delete item</span>149</Button>150151// CSS for sr-only152.sr-only {153position: absolute;154width: 1px;155height: 1px;156padding: 0;157margin: -1px;158overflow: hidden;159clip: rect(0, 0, 0, 0);160white-space: nowrap;161border-width: 0;162}163```164165### Live Regions166167Announce dynamic content:168169```tsx170<div aria-live="polite" aria-atomic="true">171{message}172</div>173174// For urgent updates175<div aria-live="assertive">176{error}177</div>178```179180Toast component includes live region:181```tsx182const { toast } = useToast()183184toast({185title: "Success",186description: "Profile updated"187})188// Announced to screen readers automatically189```190191## Form Accessibility192193### Labels and Descriptions194195**Always label inputs:**196```tsx197import { Label } from "@/components/ui/label"198import { Input } from "@/components/ui/input"199200<div>201<Label htmlFor="email">Email</Label>202<Input id="email" type="email" />203</div>204```205206**Add descriptions:**207```tsx208import { FormDescription, FormMessage } from "@/components/ui/form"209210<FormItem>211<FormLabel>Username</FormLabel>212<FormControl>213<Input {...field} />214</FormControl>215<FormDescription>216Your public display name217</FormDescription>218<FormMessage /> {/* Error messages */}219</FormItem>220```221222### Error Handling223224Announce errors to screen readers:225226```tsx227<FormField228control={form.control}229name="email"230render={({ field, fieldState }) => (231<FormItem>232<FormLabel>Email</FormLabel>233<FormControl>234<Input235{...field}236aria-invalid={!!fieldState.error}237aria-describedby={fieldState.error ? "email-error" : undefined}238/>239</FormControl>240<FormMessage id="email-error" />241</FormItem>242)}243/>244```245246### Required Fields247248Indicate required fields:249250```tsx251<Label htmlFor="name">252Name <span className="text-destructive">*</span>253<span className="sr-only">(required)</span>254</Label>255<Input id="name" required />256```257258### Fieldset and Legend259260Group related fields:261262```tsx263<fieldset>264<legend className="text-lg font-semibold mb-4">265Contact Information266</legend>267<div className="space-y-4">268<FormField name="email" />269<FormField name="phone" />270</div>271</fieldset>272```273274## Component-Specific Patterns275276### Accordion277278```tsx279import { Accordion } from "@/components/ui/accordion"280281<Accordion type="single" collapsible>282<AccordionItem value="item-1">283<AccordionTrigger>284{/* Includes aria-expanded, aria-controls automatically */}285Is it accessible?286</AccordionTrigger>287<AccordionContent>288{/* Hidden when collapsed, announced when expanded */}289Yes. Follows WAI-ARIA design pattern.290</AccordionContent>291</AccordionItem>292</Accordion>293```294295### Tabs296297```tsx298import { Tabs } from "@/components/ui/tabs"299300<Tabs defaultValue="account">301<TabsList role="tablist">302{/* Arrow keys navigate, Space/Enter activates */}303<TabsTrigger value="account">Account</TabsTrigger>304<TabsTrigger value="password">Password</TabsTrigger>305</TabsList>306<TabsContent value="account">307{/* Hidden unless selected, aria-labelledby links to trigger */}308Account content309</TabsContent>310</Tabs>311```312313### Select314315```tsx316import { Select } from "@/components/ui/select"317318<Select>319<SelectTrigger aria-label="Choose theme">320<SelectValue placeholder="Theme" />321</SelectTrigger>322<SelectContent>323{/* Keyboard navigable, announced to screen readers */}324<SelectItem value="light">Light</SelectItem>325<SelectItem value="dark">Dark</SelectItem>326</SelectContent>327</Select>328```329330### Checkbox and Radio331332```tsx333import { Checkbox } from "@/components/ui/checkbox"334import { Label } from "@/components/ui/label"335336<div className="flex items-center space-x-2">337<Checkbox id="terms" aria-describedby="terms-description" />338<Label htmlFor="terms">Accept terms</Label>339</div>340<p id="terms-description" className="text-sm text-muted-foreground">341You agree to our Terms of Service and Privacy Policy342</p>343```344345### Alert346347```tsx348import { Alert } from "@/components/ui/alert"349350<Alert role="alert">351{/* Announced immediately to screen readers */}352<AlertTitle>Error</AlertTitle>353<AlertDescription>354Your session has expired355</AlertDescription>356</Alert>357```358359## Color Contrast360361Ensure sufficient contrast between text and background.362363**WCAG Requirements:**364- **AA**: 4.5:1 for normal text, 3:1 for large text365- **AAA**: 7:1 for normal text, 4.5:1 for large text366367**Check defaults:**368```tsx369// Good: High contrast370<p className="text-gray-900 dark:text-gray-100">Text</p>371372// Avoid: Low contrast373<p className="text-gray-400 dark:text-gray-600">Hard to read</p>374```375376**Muted text:**377```tsx378// Use semantic muted foreground379<p className="text-muted-foreground">380Secondary text with accessible contrast381</p>382```383384## Focus Indicators385386Always provide visible focus indicators:387388**Default focus ring:**389```tsx390<Button className="focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">391Button392</Button>393```394395**Custom focus styles:**396```tsx397<a href="#" className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:underline">398Link399</a>400```401402**Don't remove focus styles:**403```tsx404// Avoid405<button className="focus:outline-none">Bad</button>406407// Use focus-visible instead408<button className="focus-visible:ring-2">Good</button>409```410411## Motion and Animation412413Respect reduced motion preference:414415```css416@media (prefers-reduced-motion: reduce) {417* {418animation-duration: 0.01ms !important;419animation-iteration-count: 1 !important;420transition-duration: 0.01ms !important;421}422}423```424425In components:426```tsx427<div className="transition-all motion-reduce:transition-none">428Respects user preference429</div>430```431432## Testing Checklist433434- [ ] All interactive elements keyboard accessible435- [ ] Focus indicators visible436- [ ] Screen reader announces all content correctly437- [ ] Form errors announced and associated438- [ ] Color contrast meets WCAG AA439- [ ] Semantic HTML used440- [ ] ARIA labels provided for icon-only buttons441- [ ] Modal/dialog focus trap works442- [ ] Dropdown/select keyboard navigable443- [ ] Live regions announce updates444- [ ] Respects reduced motion preference445- [ ] Works with browser zoom up to 200%446- [ ] Tab order logical447- [ ] Skip links provided for navigation448449## Tools450451**Testing tools:**452- Lighthouse accessibility audit453- axe DevTools browser extension454- NVDA/JAWS screen readers455- Keyboard-only navigation testing456- Color contrast checkers (Contrast Ratio, WebAIM)457458**Automated testing:**459```bash460npm install -D @axe-core/react461```462463```tsx464import { useEffect } from 'react'465466if (process.env.NODE_ENV === 'development') {467import('@axe-core/react').then((axe) => {468axe.default(React, ReactDOM, 1000)469})470}471```472