88 lines
2.7 KiB
Markdown
88 lines
2.7 KiB
Markdown
# Accessibility
|
||||
|
|
|
|||
|
|
Accessibility is priority #1 in the design system hierarchy. Visual polish never overrides perceivable, operable UI.
|
|||
|
|
|
|||
|
|
## Color contrast
|
|||
|
|
|
|||
|
|
- Body text (`foreground` on `background`) and muted text must remain readable in both themes.
|
|||
|
|
- Do not place `muted-foreground` on muted backgrounds without checking contrast.
|
|||
|
|
- Primary buttons use white label on `#29a19c`—do not lower primary opacity on labels.
|
|||
|
|
- Never convey meaning by color alone (pair with text/icon).
|
|||
|
|
|
|||
|
|
## Focus ring
|
|||
|
|
|
|||
|
|
Visible focus is mandatory for keyboard users.
|
|||
|
|
|
|||
|
|
| Control | Pattern |
|
|||
|
|
| --- | --- |
|
|||
|
|
| Button | `focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:border-ring` |
|
|||
|
|
| Input | `focus-visible:ring-[3px] focus-visible:ring-primary/25 focus-visible:border-primary` |
|
|||
|
|
| Generic | Prefer ring tokens; never `outline-none` without replacement |
|
|||
|
|
|
|||
|
|
Do not remove focus styles from cards/links that are interactive.
|
|||
|
|
|
|||
|
|
## Keyboard navigation
|
|||
|
|
|
|||
|
|
- All buttons, links, and controls reachable via Tab.
|
|||
|
|
- Menus / sheets / dialogs use Radix primitives (focus trap + Escape).
|
|||
|
|
- Maintain logical RTL tab order; avoid positive `tabIndex` except when necessary.
|
|||
|
|
- Icon-only controls need accessible names.
|
|||
|
|
|
|||
|
|
## ARIA expectations
|
|||
|
|
|
|||
|
|
| Case | Requirement |
|
|||
|
|
| --- | --- |
|
|||
|
|
| Icon button | `aria-label` |
|
|||
|
|
| Decorative icon/image | `aria-hidden` / empty `alt` |
|
|||
|
|
| Expandable | `aria-expanded` + controls id |
|
|||
|
|
| Dialogs / sheets | Titled (`SheetTitle`, `DialogTitle`) — `sr-only` if visually hidden |
|
|||
|
|
| Invalid fields | `aria-invalid` + error text |
|
|||
|
|
| Landmark nav | `aria-label` on primary `<nav>` |
|
|||
|
|
| Decorative sections | `aria-hidden` on `.section-decor` |
|
|||
|
|
|
|||
|
|
Prefer native semantics (`button`, `a`, `label`) before ARIA.
|
|||
|
|
|
|||
|
|
## Reduced motion
|
|||
|
|
|
|||
|
|
- Honor `prefers-reduced-motion`.
|
|||
|
|
- `MotionReveal` already disables motion.
|
|||
|
|
- New CSS animations need a reduced-motion override.
|
|||
|
|
- Do not autoplay motion-critical content.
|
|||
|
|
|
|||
|
|
## Touch targets
|
|||
|
|
|
|||
|
|
| Control | Minimum |
|
|||
|
|
| --- | --- |
|
|||
|
|
| Default button | 40×40 (`h-10`) |
|
|||
|
|
| Large button | 48×48 |
|
|||
|
|
| Icon button | 40×40 |
|
|||
|
|
| Compact button | 32×32 — avoid for primary mobile actions |
|
|||
|
|
| Input | ≥ 36px height (`h-9`); 48px when paired with lg submit |
|
|||
|
|
|
|||
|
|
## Forms
|
|||
|
|
|
|||
|
|
- Visible labels (not placeholder-only).
|
|||
|
|
- Error text associated with fields.
|
|||
|
|
- Preserve browser autofill / zoom-safe 16px text on mobile inputs.
|
|||
|
|
|
|||
|
|
## Images & media
|
|||
|
|
|
|||
|
|
- Meaningful `alt`.
|
|||
|
|
- Do not flash high-contrast animations.
|
|||
|
|
|
|||
|
|
## Checklist snippet
|
|||
|
|
|
|||
|
|
- [ ] Keyboard only can complete the flow
|
|||
|
|
- [ ] Focus visible on all interactive elements
|
|||
|
|
- [ ] Contrast OK in light and dark
|
|||
|
|
- [ ] Reduced motion OK
|
|||
|
|
- [ ] SR labels on icon buttons
|
|||
|
|
- [ ] No keyboard trap outside modals
|
|||
|
|
|
|||
|
|
## Related
|
|||
|
|
|
|||
|
|
- [forms.md](./forms.md)
|
|||
|
|
- [motion.md](./motion.md)
|
|||
|
|
- [dark-mode.md](./dark-mode.md)
|
|||
|
|
- Cursor rule: `.cursor/rules/accessibility.mdc`
|