Book a callGitHub1

Concepts05/07

Accessibility floors

Updated · Edit on GitHub

Every design system OpenDesigner makes gets a set of accessibility minimums, called floors. They are WCAG 2.2 AA text contrast, 3:1 contrast for borders and focus rings, minimum target sizes, a visible focus ring and a reduced-motion mode. Fixed rules check them before the AI gives any opinion, and an error blocks the build.

The floors

Text contrast

  • Body text: at least 4.5:1 against its background. This is WCAG 2.2 success criterion 1.4.3 Contrast (Minimum).
  • Large text: at least 3:1. WCAG defines large text as at least 18 point, or 14 point bold.

The engine measures with the WCAG 2 formula and never rounds up, so 4.49:1 fails. It checks the text and background pairs the system defines, in every mode. In a test run, it checked 164 color pairs in every mode, across 12 mode combinations: light and dark, three densities, and standard and reduced motion.

A gray that looks close enough

On a good screen both read fine. Measured, the first is 4.49:1. The engine never rounds up, so body text in it fails and the build stops.

Aacolor.neutral.light.9
#6f7683 on #fcfdff
4.49:1
Aacolor.text.disabled as body text
#808793 on #fcfdff
3.55:1

Text roles built to pass

The floor for body text is 4.5:1, checked with the WCAG 2 formula in every mode. The text roles clear it with room to spare.

Aacolor.text.primary
#222428 on #fcfdff
15.27:1
Aacolor.text.secondary
#515762 on #fcfdff
7.14:1
Contrast is measured, not eyeballed. In OpenDesigner's own palette the neutral step 9 reads fine but measures 4.49:1 and fails the 4.5:1 floor for body text, while the text roles pass with room to spare.

Borders, focus rings and icons

Borders, focus rings and other parts that are not text need at least 3:1 contrast. See 1.4.11 Non-text Contrast.

Target sizes

Anything people tap or click must be big enough:

  • Web: at least 24 by 24 CSS pixels, from 2.5.8 Target Size (Minimum).
  • iOS: at least 44 points, from Apple's platform guidance.
  • Android: at least 48 dp, from Android's platform guidance.

A compact density never shrinks these. When a control is drawn smaller than the target, the engine notes that it needs padding to keep the tap area full size.

An icon with no padding

A 20 px icon drawn as its own button: the tap area is the glyph itself, under the 24 px floor. That is an error, and the build stops.

Padded to the target

The same 20 px icon with padding around it: 24 px for a pointer, 44 px for touch. A compact density never shrinks these.

Target sizes from the tokens: a 20 px icon needs padding so its tap area reaches 24 by 24 CSS pixels for a pointer and 44 for touch. Drawn as its own 20 px button, the target is under the floor and the engine reports an error.

A visible focus ring

Every system gets a focus ring, so keyboard users can see where they are: a 2 px ring with a 2 px gap around the element. It never relies on color alone. See 2.4.7 Focus Visible.

Focus shown by color alone

The outline is removed and a slightly darker fill is the only sign. Keyboard users can't tell where they are, and meaning shown by color alone is an error.

ButtonidleButtonfocused
:focus {
  outline: none;
  background: #1e2fe6;
}

A visible ring with a gap

A 2 px ring with a 2 px gap around the element, in a color that keeps 3:1 against every surface.

ButtonidleButtonfocused
ring
--ds-focus-ring-width 2px
gap
--ds-focus-ring-offset 2px
color
--ds-color-border-focus
The focus ring every system gets: a 2 px ring with a 2 px gap in the focus color, which stays at 3:1 against every surface. A darker fill with the outline removed relies on color alone, which the checks report as an error.

Reduced motion, and glass surfaces

Reduced motion is a mode in the tokens. It follows the device setting, and the CSS export also lets you switch it with a data-motion attribute. OpenDesigner treats 2.3.3 Animation from Interactions as required, even though WCAG lists it at level AAA. If your system uses see-through glass surfaces, each one gets a solid version for people who turn transparency off. The CSS export swaps it in, with no blur, when the browser reports that the person wants less transparency (prefers-reduced-transparency: Chrome and Edge today; Safari does not report it and Firefox only behind a setting) or more contrast (prefers-contrast: more, in every major browser). A glass surface without one is an error.

Text that can grow

Text must scale to 200%, and containers must grow with it. The CSS export sets font sizes in rem, so browser text zoom works. See 1.4.4 Resize Text. The Swift export uses Dynamic Type text styles, so text follows the person's size setting.

Errors and warnings

The checks give the same result every time.

  • Errors break an accessibility rule and block the build. Examples: targets under 24 px, fields with no label, meaning shown by color alone (see 1.4.1 Use of Color), and dialogs with no way out. You can waive one only with a written reason.
  • Warnings come from practitioner advice and are labelled as guidance. Examples: too many type sizes in one view, or very bright colors in a very dense layout. You can override a warning with a reason.
  • Notes are advice only. For example, the engine reports an APCA reading-contrast score next to the WCAG ratio. WCAG 3 is still a draft, so WCAG 2.2 is the target it enforces.

Your brand color and contrast

A brand color you typed stays exact. On the main buttons the engine puts white or dark text on it, whichever reads. Only when neither reads do buttons with text use a nearby readable shade, and your exact color stays for the logo and large areas. The engine tells you which in one line, and the overview page shows the result with its contrast ratio.

The floors stay locked

The floors are set in the sketch and never skipped. They are locked unless you raise them. For example, you can set a stricter contrast target of AAA. When rules clash, the floors come first, ahead of your own explicit choices and any standard.

The note OpenDesigner offers to add to your AGENTS.md tells coding agents that the floors are part of the system.

What the checks don't cover

The floors are a minimum. Passing them does not prove your app meets all of WCAG.

  • Automated checks catch only part of WCAG. Many success criteria need a person to judge them, such as whether link text makes sense or whether the reading order is right.
  • The engine checks the design system's values. It can't see how your screens use them. A button can still be built without a label.
  • People who use screen readers and other assistive technology should test your app. At deeper zoom levels, the interview asks which screen readers, control tools and text sizes you will test on each kind of device.

Sources