Get started
Get started
Accessibility guidelines
Foundry targets WCAG 2.2 Level AA for product UI. These guidelines cover contrast, keyboard, focus, forms, motion, media, and how to compose Radix-based components without breaking assistive tech.
Accessibility is a requirement, not a polish pass
Components ship accessible primitives (Radix). You still own copy, hierarchy, color choices in custom layouts, alternative text, and verifying full flows with keyboard and a screen reader.
Standard & scope
Perceivable
Information and UI must be presentable in ways users can perceive — contrast, text alternatives, captions, and responsive reflow.
Operable
Everyone can operate controls — full keyboard support, adequate touch targets, no keyboard traps, enough time for tasks.
Understandable
UI reads clearly — labels, consistent navigation, error messages that explain how to fix issues, predictable focus moves.
Robust
Works with assistive tech — valid semantics, ARIA only when needed, components that expose the right roles and states.
What Foundry guarantees vs what you own
- Guaranteed: focus rings, keyboard patterns in primitives, semantic roles from Radix, default 44px control height, skip link in the docs shell
- You own: page headings, form error text, icon-only names, custom color overrides, media captions, live region announcements for async work
Perceivable
Color & contrast
| Pair | Target | Use |
|---|---|---|
| foreground on background | ≥ 4.5:1 (AA body) | Primary reading text |
| muted-foreground on background | ≥ 4.5:1 preferred; ≥ 3:1 min for large | Secondary labels only — not long paragraphs |
| primary-foreground on primary | ≥ 4.5:1 | Buttons and solid chips |
| UI components / icons | ≥ 3:1 against adjacent colors | Borders of inputs, icon glyphs that convey state |
Body on background
Primary reading text uses text-foreground on bg-background or bg-card.
Muted is secondary only
Helper text and meta — never the only place an error or state is explained. Pair status with icon + text.
Color is never the only signal
Text alternatives & media
- Informative images: meaningful
alt - Decorative images / illustrations:
alt=""oraria-hidden - Icon-only controls: accessible name via aria-label or sr-only text
- Video: captions when speech/info is present; never autoplay sound
- Share thumbnails and spatial stages are decorative for AT unless they convey unique content — then describe nearby in text
Operable
Keyboard
Every interactive control is reachable and activatable without a pointer. No traps outside intentional modals.
Focus visible
ring-2 ring-ring with offset on background. Never outline: none without a visible replacement.
Touch targets
Default Button/Input height h-11 (44px). Compact sizes only for dense desktop chrome.
Focus & skip link
Docs shell includes a skip link; replicate the pattern in product apps.
Do
- Match DOM order to visual reading order
- Trap focus only inside modal dialogs/sheets while open
- Return focus to the control that opened an overlay
- Allow Esc to dismiss non-critical overlays
Don’t
- Use positive tabindex (>0) to force order
- Remove focus outlines for aesthetics
- Open modals without moving focus into them
- Require a hover-only path for critical actions
Understandable
Labeled fields & errors
Visible labels, associated errors, and non-color status.
Enter a valid email address.
Form needs attention
- Use one
h1per view; nest headings without skipping levels - Link text describes destination — avoid “click here”
- Language: set
langonhtml(Foundry usesen) - Async results: toast for transient success;
role="alert"or polite live region for persistent errors
Component rules
| Prop | Type | Default | Description |
|---|---|---|---|
| Button (icon-only) | aria-label required | — | Accessible name when no visible text |
| Input | Label htmlFor + id | — | Never rely on placeholder alone |
| Dialog | Title + Description | — | DialogTitle required; Description when context helps |
| Alert Dialog | Explicit Cancel + Action | — | Destructive confirm must name the consequence |
| Tabs | Labels on triggers | — | Do not use icons-only tabs without names |
| Toast | Short, non-critical | — | Do not put the only error recovery path in a toast |
| Table | th scope / captions | — | Use TableHead for headers; caption for purpose |
| Images / SVG | alt or aria-hidden | — | Decorative system illustrations are aria-hidden |
Prefer composition over ARIA hacks
Keyboard matrix
Behavior is provided by Radix primitives under Foundry wrappers. Verify after any customization of focus management.
| Component | Keys | Notes |
|---|---|---|
| Button / Link | Enter, Space (button); Enter (link) | Visible focus; icon-only needs accessible name |
| Dialog / Alert Dialog | Tab / Shift+Tab cycle; Esc close | Focus trap; restore focus to trigger on close |
| Sheet / Drawer | Tab cycle; Esc close | Same as dialog; ensure dismiss control is reachable |
| Dropdown / Menu | Arrow Up/Down; Home/End; Enter; Esc; typeahead | Roving tabindex inside menu |
| Select | Space/Enter open; arrows; typeahead; Enter select; Esc | Announce selected value to AT |
| Tabs | Left/Right (or Up/Down); Home/End | Tab key leaves the tablist into panel |
| Accordion / Collapsible | Enter/Space toggle; arrows optional in groups | aria-expanded must reflect state |
| Checkbox / Switch / Radio | Space toggle; arrows in radio group | Label activates control via htmlFor |
| Slider | Arrows; Page Up/Down; Home/End | Expose value via aria-valuenow / text |
| Command / Combobox | Type to filter; arrows; Enter; Esc | aria-activedescendant or roving focus |
| Tooltip | Focus shows; Esc dismisses | Do not put essential info only in tooltip |
Motion, bandwidth & immersive
Reduced motion
Honor prefers-reduced-motion. Use Framer MotionConfig reducedMotion="user" and CSS media queries. Prefer opacity/transform; avoid large layout shifts.
Illusions & 3D
Background illusions and spatial stages are decorative — keep content contrast. Gate WebGL/video with Bandwidth lite mode. Three.js scenes need a non-WebGL fallback summary.
Reduced motion CSS
Motion tokens live under Foundations → Motion and src/lib/motion.ts.
Testing checklist
Minimum bar before shipping a screen
- Tab through the full view — order matches visual layout; no traps
- Every control shows a visible focus ring
- Icon-only buttons announce a name
- Forms: labels, errors, and submit states are announced
- Dialogs: focus moves in, Esc closes, focus returns
- Images: informative alt or decorative hidden
- Color: status not hue-only; body text meets AA
- Zoom to 200% — no loss of content or horizontal scroll for text
- prefers-reduced-motion: no essential info only in animation
- Mobile 390px: targets ≥ 44px; primary actions reachable