Skip to content

Get started

Get started

WCAG 2.2 AA

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.

POUR principles
44×44px targets
Focus never removed
Reduced motion

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

PairTargetUse
foreground on background≥ 4.5:1 (AA body)Primary reading text
muted-foreground on background≥ 4.5:1 preferred; ≥ 3:1 min for largeSecondary labels only — not long paragraphs
primary-foreground on primary≥ 4.5:1Buttons and solid chips
UI components / icons≥ 3:1 against adjacent colorsBorders 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

Errors need text (and ideally an icon). Charts need patterns or labels, not hue alone. Destructive actions use label copy, not just red.

Text alternatives & media

  • Informative images: meaningful alt
  • Decorative images / illustrations: alt="" or aria-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.

  • Use one h1 per view; nest headings without skipping levels
  • Link text describes destination — avoid “click here”
  • Language: set lang on html (Foundry uses en)
  • Async results: toast for transient success; role="alert" or polite live region for persistent errors

Component rules

PropTypeDefaultDescription
Button (icon-only)aria-label requiredAccessible name when no visible text
InputLabel htmlFor + idNever rely on placeholder alone
DialogTitle + DescriptionDialogTitle required; Description when context helps
Alert DialogExplicit Cancel + ActionDestructive confirm must name the consequence
TabsLabels on triggersDo not use icons-only tabs without names
ToastShort, non-criticalDo not put the only error recovery path in a toast
Tableth scope / captionsUse TableHead for headers; caption for purpose
Images / SVGalt or aria-hiddenDecorative system illustrations are aria-hidden

Prefer composition over ARIA hacks

Use Foundry Dialog instead of a div with role=dialog. Use Checkbox instead of clickable divs. Custom ARIA is a last resort — wrong ARIA is worse than none.

Keyboard matrix

Behavior is provided by Radix primitives under Foundry wrappers. Verify after any customization of focus management.

ComponentKeysNotes
Button / LinkEnter, Space (button); Enter (link)Visible focus; icon-only needs accessible name
Dialog / Alert DialogTab / Shift+Tab cycle; Esc closeFocus trap; restore focus to trigger on close
Sheet / DrawerTab cycle; Esc closeSame as dialog; ensure dismiss control is reachable
Dropdown / MenuArrow Up/Down; Home/End; Enter; Esc; typeaheadRoving tabindex inside menu
SelectSpace/Enter open; arrows; typeahead; Enter select; EscAnnounce selected value to AT
TabsLeft/Right (or Up/Down); Home/EndTab key leaves the tablist into panel
Accordion / CollapsibleEnter/Space toggle; arrows optional in groupsaria-expanded must reflect state
Checkbox / Switch / RadioSpace toggle; arrows in radio groupLabel activates control via htmlFor
SliderArrows; Page Up/Down; Home/EndExpose value via aria-valuenow / text
Command / ComboboxType to filter; arrows; Enter; Escaria-activedescendant or roving focus
TooltipFocus shows; Esc dismissesDo 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

Keyboard-only pass, one screen reader skim (VoiceOver, NVDA, or TalkBack), and a contrast check on custom colors. Automated tools catch ~30–40% — they do not replace human checks.
  1. Tab through the full view — order matches visual layout; no traps
  2. Every control shows a visible focus ring
  3. Icon-only buttons announce a name
  4. Forms: labels, errors, and submit states are announced
  5. Dialogs: focus moves in, Esc closes, focus returns
  6. Images: informative alt or decorative hidden
  7. Color: status not hue-only; body text meets AA
  8. Zoom to 200% — no loss of content or horizontal scroll for text
  9. prefers-reduced-motion: no essential info only in animation
  10. Mobile 390px: targets ≥ 44px; primary actions reachable