一键导入
accessibility
WCAG 2.1 AA accessibility patterns for droneroute — color contrast, ARIA, keyboard navigation, Leaflet map a11y, and Radix UI component checklist.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
WCAG 2.1 AA accessibility patterns for droneroute — color contrast, ARIA, keyboard navigation, Leaflet map a11y, and Radix UI component checklist.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
PR-based development workflow for droneroute. Use when implementing features, fixing bugs, or making any code changes. Covers branch creation, CI checks, changelog entries, and PR creation.
Playwright testing patterns for droneroute. Covers screenshot automation, test structure, and CI integration.
Security patterns for droneroute — JWT auth guards, input validation, SQL injection prevention, and environment variable safety.
GitHub Issues tracking workflow for droneroute. Use when managing tasks, features, and bugs.
Before/after screenshot workflow for UI PRs. Covers capture techniques, annotation, and embedding in PRs and changelogs.
| name | accessibility |
| description | WCAG 2.1 AA accessibility patterns for droneroute — color contrast, ARIA, keyboard navigation, Leaflet map a11y, and Radix UI component checklist. |
Guide writing accessible UI components and fixing accessibility violations in the droneroute codebase. This skill covers WCAG 2.1 AA compliance patterns specific to the Leaflet map interface, Radix UI primitives, and Tailwind CSS v4.
WCAG 2.1 AA — the mid-tier accessibility standard. Key requirements:
| Criterion | Requirement |
|---|---|
| 1.4.3 Contrast (Minimum) | Text: 4.5:1, Large text (18px+ bold or 24px+): 3:1 |
| 1.4.11 Non-text Contrast | UI components and graphical objects: 3:1 |
| 2.1.1 Keyboard | All functionality available via keyboard |
| 2.4.1 Bypass Blocks | Skip navigation link to bypass repeated content |
| 2.4.7 Focus Visible | Keyboard focus indicator is visible |
| 1.3.1 Info and Relationships | Semantic HTML structure |
The map (react-leaflet) is the primary UI element. Maps are inherently challenging for accessibility.
+/- to zoomtabIndex={0})aria-label to the map container describing its purpose:
<MapContainer aria-label="Mission planning map">
aria-label:
<Marker position={pos} title={`Waypoint ${index + 1}: ${lat.toFixed(5)}, ${lng.toFixed(5)}`}>
This app uses @radix-ui/react-* components. Most handle accessibility automatically, but some need attention.
Radix Dialog handles focus trapping and aria-* attributes automatically. Ensure:
DialogTitlearia-label="Close" if icon-only<Select>
<SelectTrigger aria-label="Select mission type">
<SelectValue placeholder="Choose type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="orbit">Orbit</SelectItem>
</SelectContent>
</Select>
Always add tooltips to icon-only buttons:
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" aria-label="Delete waypoint">
<Trash2 className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Delete waypoint</TooltipContent>
</Tooltip>
Radix Slider needs an accessible label:
<div>
<Label htmlFor="altitude">Altitude (m)</Label>
<Slider id="altitude" aria-label="Altitude in meters" value={[altitude]} />
</div>
Every button with only an icon and no visible text MUST have aria-label:
<Button variant="ghost" size="icon" aria-label="Close panel">
<X className="h-4 w-4" />
</Button>
Every input must have an associated label:
// Option 1: visible label
<Label htmlFor="speed">Speed (m/s)</Label>
<Input id="speed" type="number" />
// Option 2: aria-label (when no visible label)
<Input aria-label="Search waypoints" placeholder="Search..." />
Don't rely on color alone. Use both color and text/icons:
// BAD — color only
<div className={mission.synced ? "text-green-500" : "text-red-500"}>●</div>
// GOOD — color + text
<div className={mission.synced ? "text-green-500" : "text-red-500"}>
{mission.synced ? "Synced" : "Not synced"}
</div>
focus-visible styles:
.custom-button:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
tabIndex values (disrupts natural order)tabIndex={0} is fine for making non-interactive elements focusable when adding role and keyboard handlers<button>, <a>) over role="button" on divs/spansaria-expandedWhen adding a new interactive component:
aria-label<label> (or aria-label / aria-labelledby)<button>, <a>) — avoid role="button" on divsalt text (decorative images use alt="")npm run lint — oxlint includes jsx-a11y rulesConsider adding @axe-core/playwright to the project for automated WCAG scanning:
import AxeBuilder from "@axe-core/playwright";
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
.analyze();
expect(results.violations).toEqual([]);
This would catch violations automatically in CI. Track this via a GitHub Issue when ready to implement.