Floor
Accessibility Floor — Synthesis
How to use this document
This document states requirements as constraints. Every "must" is either a WCAG 2.2 AA requirement (the current legal standard for most regulated products) or a well-established practical minimum that any professional design system must satisfy. Each requirement includes a verification method.
Reference this document at the end of every campaign step that produces CSS, components, or interaction patterns. It is a checklist, not a guide.
1 — Color contrast
1.1 Text contrast (WCAG 1.4.3 — AA)
Normal text (< 18pt / < 14pt bold) must have ≥ 4.5:1 contrast against its background.
Normal text in web contexts means: body text, labels, captions, placeholder text, button labels, link text — any text under 24px regular weight or under ~18.67px bold weight.
Large text (≥ 18pt / ≥ 14pt bold) must have ≥ 3:1 contrast against its background.
Large text in web contexts: headings at 24px+ (regular), or ~19px+ (bold).
Verify: WebAIM Contrast Checker (webaim.org/resources/contrastchecker), browser DevTools color picker, or the Colour Contrast Analyser desktop app. Enter foreground hex + background hex; confirm the ratio meets the threshold for the text size.
Exemptions: Disabled (inactive) text, decorative text that conveys no information, logotypes. Placeholder text is not exempt — it is text.
1.2 UI component contrast (WCAG 1.4.11 — AA)
Visual boundaries that identify interactive components must have ≥ 3:1 contrast against adjacent colors.
This applies to: input field borders, checkbox and radio outlines, toggle switch tracks, button outlines (on outlined/ghost variants), focus ring outlines when author-provided, chart lines and data markers when required for comprehension.
Verify: Measure the border or boundary element against the surface it sits on (not the element's content). A #cccccc border on white background = 1.6:1 — fails. A #767676 border on white = 4.54:1 — passes.
Exemptions: Filled buttons where the background color itself identifies the button (the background must still meet 3:1 against the page background). Disabled components.
1.3 Dark mode contrast is a separate verification
Contrast must be verified independently in dark mode. WCAG 2.x contrast ratios do not automatically transfer between modes.
The WCAG formula overstates perceived contrast for near-dark color pairs. A token pair that passes 4.5:1 in light mode may pass the formula in dark mode while being functionally harder to read, because the formula is not perceptually uniform at the low-luminance end.
Verify: Run contrast checks against your dark mode token values explicitly. If your design system uses tonal palettes (HCT/OKLab), verify tone differences are ≥ 50 for normal text, ≥ 40 for large text and UI, in addition to running the WCAG formula.
APCA as a supplemental check: APCA (Lc values) is perceptually uniform and more reliable for dark mode. Target Lc ≥ 75 for body text, Lc ≥ 60 for informational text, Lc ≥ 45 for headings. APCA is not yet normative (WCAG 2.x remains the legal standard), but use it to catch dark-mode false positives — pairs that pass WCAG 2.x but are visually unreadable.
1.4 No raw color values in components
Components must consume color tokens. No hardcoded hex values, HSL declarations, or inline rgba() in component styles.
Verify: Search the component codebase for #, rgb(, hsl(, and rgba( in style rules. Each match is a violation unless it is in the token definition itself.
2 — Keyboard navigation
2.1 All interactive elements must be keyboard-reachable (WCAG 2.1.1 — A)
Every interactive element must be reachable and operable by keyboard alone. No functionality may be available only via mouse, touch, or pointer.
Required keyboard behaviors:
- Button:
EnterorSpaceto activate - Link:
Enterto activate - Checkbox:
Spaceto toggle; arrow keys to move between grouped options - Radio group: arrow keys to navigate between options
- Modal:
Tab/Shift+Tabwithin modal;Escapeto close - Dropdown menu: arrow keys to navigate;
EnterorSpaceto select;Escapeto close - Tab panel: arrow keys between tabs;
Tabmoves into panel content
Verify: Unplug the mouse. Tab through every interactive element in the component. Confirm every element is reachable and can be activated. Any element that cannot be reached or operated is a violation.
Custom components built on non-semantic elements (div, span) must add tabindex="0" and keyboard event handlers. Any drag-and-drop pattern must have a keyboard alternative.
2.2 No keyboard traps (WCAG 2.1.2 — A)
Focus must never become permanently stuck in a component. If keyboard focus can enter a component, it must be able to exit.
Note: trapping focus within a modal dialog is correct and required behavior. What is forbidden is making it impossible to dismiss the modal and release focus.
Verify: Tab into every interactive component. Confirm you can always tab out, close, or otherwise move focus away using only the keyboard.
2.3 Tab order must follow visual order
The tab order must match the visual reading order. Users should not find focus jumping unexpectedly across the layout.
Verify: Tab through a page from top to bottom. Confirm focus moves in the same order a sighted user would visually scan the content. If focus jumps, check for positive tabindex values (tabindex > 0) — these override natural DOM order and are almost always incorrect. Remove them.
3 — Focus visibility
3.1 Focus indicator must be visible (WCAG 2.4.7 — AA)
Keyboard focus must always be visible. The focused element must be distinguishable from its unfocused state.
outline: none or outline: 0 without a replacement focus style is a WCAG violation.
Minimum focus style:
:focus-visible {
outline: 2px solid;
outline-offset: 2px;
}
The :focus-visible pseudo-class is correct for implementations that show focus only during keyboard navigation. :focus alone shows the ring on mouse-click as well — both approaches are compliant; :focus-visible is the modern preference.
Verify: Tab through the page. Confirm every focused element has a visible, distinguishable indicator. If you can't immediately see which element has focus, the indicator is insufficient.
3.2 Focus indicator contrast (WCAG 1.4.11 applied to focus rings)
The focus ring must have ≥ 3:1 contrast against the adjacent background color.
A 2px solid #005fcc ring on white background = ~5.9:1 — passes. A light gray ring on a white background will fail.
Verify: Measure the focus ring color against the surface it appears on using the contrast checker. Meet 3:1 minimum; target higher.
3.3 Focused element must not be entirely hidden (WCAG 2.4.11 — AA)
When an element receives focus, it must not be completely covered by sticky headers, cookie banners, fixed navigation, or other author-created overlays.
Partial obscuration is permitted at AA. Complete concealment is not.
Fix pattern:
html {
scroll-padding-top: [sticky-header-height + 8px];
}
Verify: Tab through a page with a sticky header. Confirm no focused element is fully hidden behind the header when scrolled into view.
4 — Touch and pointer targets
4.1 Interactive targets must be at minimum 24×24 CSS px (WCAG 2.5.8 — AA)
Every pointer/touch target must be at least 24×24 CSS pixels in its interactive area, or positioned so that no other target falls within a 24px-diameter circle centered on its bounding box.
Verify: Inspect the interactive area (not the visual icon) of every small control — icon buttons, close buttons, checkbox hit areas, inline links. Measure in DevTools. Any target below 24×24px that isn't spaced away from other targets is a violation.
4.2 Target size best practice is 44×44 CSS px (WCAG 2.5.5 — AAA, industry standard)
Target 44×44 CSS px for all primary interactive controls. This is the AAA threshold, but it is the industry standard for usability (Apple HIG, Material Design, and Carbon all specify 44px minimum).
The 24px requirement is the legal minimum. The 44px target is what makes the interface comfortable for users with motor impairments and on small touchscreens.
Implementation:
button, [role="button"], a {
min-height: 44px;
min-width: 44px;
}
Use padding to expand the interactive area without changing visual size. An icon button with a 20px icon needs 12px of padding on each side to meet 44px.
Verify: Inspect the height and width of every button, link, and interactive control in DevTools. Confirm the interactive area (including padding) meets the target.
5 — Semantic structure and ARIA
5.1 Semantic HTML first (WCAG 4.1.2 — A)
Use semantic HTML elements for all interactive components before reaching for ARIA. Native elements carry implicit roles, keyboard behavior, and accessibility semantics that custom elements must re-implement manually.
| Use this | Not this |
|---|---|
<button> | <div role="button" tabindex="0"> |
<a href> | <span role="link" tabindex="0"> |
<input type="checkbox"> | <div role="checkbox" tabindex="0"> |
<select> | Custom dropdown from scratch |
<nav>, <main>, <header> | <div id="nav">, <div id="content"> |
ARIA is for patterns that have no semantic HTML equivalent: tabs, accordions, comboboxes, tree views, data grids, custom modals.
Verify: Inspect the DOM of every interactive component. Every div or span with a click handler must have either a semantic equivalent or a full ARIA implementation (role + name + state + keyboard behavior).
5.2 Every interactive element must have an accessible name (WCAG 4.1.2 — A)
Screen readers announce the accessible name when an element receives focus. An element with no accessible name is announced as its role only (e.g. "button") — no indication of what it does.
Accessible name sources in priority order:
aria-labelledby— references another element's textaria-label— direct string label<label for>— paired label element (forms)- Element text content — for buttons and links
Common violations:
- Icon-only button:
<button><svg>...</svg></button>— addaria-label="Close"or a visually hidden text span - Form input with placeholder only:
<input placeholder="Email">— placeholder is not a label; add<label for> - Image that conveys information:
<img src="chart.png">with noaltattribute
Verify: Use a screen reader (VoiceOver, NVDA, or JAWS) and tab to every interactive element. Confirm the announcement includes a meaningful name that describes the element's purpose.
5.3 Dynamic state must be programmatically exposed (WCAG 4.1.2 — A)
When a component's state changes, the change must be communicated to assistive technologies via ARIA attributes — not CSS class changes alone.
Required state attributes:
| State | ARIA attribute |
|---|---|
| Expanded / collapsed | aria-expanded="true/false" |
| Selected | aria-selected="true/false" |
| Checked | aria-checked="true/false/mixed" |
| Pressed (toggle) | aria-pressed="true/false" |
| Disabled | aria-disabled="true" or HTML disabled |
| Invalid (form) | aria-invalid="true" |
| Required (form) | aria-required="true" or HTML required |
Verify: Open DevTools Accessibility panel. Interact with every stateful component (open an accordion, check a checkbox, submit an invalid form). Confirm the ARIA state attribute updates to reflect the new state.
6 — Motion
6.1 Respect prefers-reduced-motion (non-negotiable)
All animated content must be disabled or substantially reduced when prefers-reduced-motion: reduce is set. This is a medical accommodation for users with vestibular disorders, photosensitive conditions, and motion sickness.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
See kb/principles/motion/architecture for the full motion system specification.
Verify: Enable "Reduce Motion" in OS accessibility settings (macOS: System Settings → Accessibility → Display; iOS: Settings → Accessibility → Motion). Reload the product. Confirm all non-essential animations stop. Functional transitions (state changes, overlay appearances) may remain brief; decorative animations must stop entirely.
7 — Text readability
7.1 Body text must be at minimum 16px (practical minimum, not WCAG)
Body text — text intended for sustained reading — must be at least 16px. This is not a WCAG criterion but is a consistent recommendation from accessibility and usability research. Text below 14px requires additional contrast to compensate for reduced legibility.
Verify: Inspect the computed font-size of all body text, descriptions, and form field content. Confirm ≥ 16px.
7.2 Line length must be within the readable range (practical minimum)
Body text columns must not exceed 90 characters per line (approximately 680px at 16px in a regular-width sans-serif). Excessively long lines cause eye-tracking errors when returning to the start of the next line.
Verify: Count characters per line in the widest body text column at the product's maximum content width. If the count exceeds 90, add a max-width constraint.
7.3 Line height must be within the readable range (WCAG 1.4.8 guidance, practical minimum)
Body text must have a line height of at least 1.5× the font size (CSS line-height: 1.5). WCAG 1.4.8 (AAA) specifies this; it is also a minimum for readable body text at any WCAG level.
Verify: Inspect the computed line-height of all body text. Confirm ≥ 1.5.