Guide 02

Contrast & accessibility

A palette that fails contrast is not a stylistic problem, it is a broken feature. Here are the thresholds, the states people forget to test, and a checker you can use on this page.

Contrast checker

—contrast ratio

    Large text sample, 24px

    Body text sample at sixteen pixels — read this at arm's length before you approve the pair.

    The thresholds that actually apply

    WCAG 2.2 keeps the same contrast requirements as 2.1 and 2.0. Three numbers cover almost every decision you will make in an interface palette.

    Minimum contrast ratios under WCAG 2.2
    ContentLevel AALevel AAANotes
    Body text under 18.66px regular4.5:17:1The default for almost all UI copy
    Large text (24px+, or 18.66px+ bold)3:14.5:1Headings, display numbers, hero copy
    UI components and graphical objects3:1—Input borders, icon strokes, focus indicators, chart lines
    Decorative, disabled or incidentalNo requirement—Still test it — "disabled" must not read as "invisible"

    The 3:1 non-text rule is the one most palettes fail. A 1px border at rgba(255,255,255,0.08) over a dark surface looks tasteful in a mockup and measures around 1.1:1. If that border is the only thing telling a user where an input field begins, it must clear 3:1 against the surface behind it.

    Test states, not just resting colours

    A generated palette produces a base value plus hover, active, focus and disabled variants. Each of those is a separate contrast pair, and hover is where systems break most often — a hover that lightens a button background by 8% can drop white label text below 4.5:1 without anyone noticing, because nobody screenshots hover states for review.

    • Default — text on surface, border on surface, icon on surface.
    • Hover — recheck every pair. If the background moves, the text pair moved with it.
    • Focus — the focus indicator needs 3:1 against both the component and the page background behind it. A ring that only contrasts with one of the two disappears half the time.
    • Active / pressed — usually the darkest or most saturated state; the usual failure is label text on an over-saturated primary.
    • Disabled — exempt from the requirement, but users still need to read the label. Aim for 3:1 anyway and signal the state with more than colour.

    What the ratio formula misses

    The WCAG contrast formula compares relative luminance in sRGB. It is simple, deterministic and implemented identically everywhere — which is why it became the standard. It is also known to be a rough model of perception. Two well-documented weak points: it is over-generous with light text on dark backgrounds in some hue ranges, and it is over-strict with certain mid-tone pairs that read perfectly well in practice.

    APCA, the contrast method being developed for WCAG 3, models polarity and font weight rather than returning a single symmetrical ratio. It is worth watching and worth testing against, but WCAG 2.x remains the requirement referenced by most procurement and legal frameworks, so it stays the pass/fail gate in Curiosityjivar. Treat APCA as a second opinion, not a replacement.

    Two things the formula cannot see at all: colour blindness and ambient light. Roughly one in twelve men and one in two hundred women have some form of colour vision deficiency, most commonly red-green. A red error state and a green success state can have identical luminance and be indistinguishable to those users while passing every automated check. Always pair colour with an icon, a label or a shape.

    A practical order of operations

    Fix the text tokens first, because they are the least negotiable. Choose your lightest and darkest text values so that they clear 4.5:1 against every surface in the ramp, then build the surfaces around them rather than the other way round. Next, set the border token to the first value that clears 3:1 against its most common background. Only then place the brand primary, and if it cannot carry white text at 4.5:1, use it as an outline or a text colour instead of a fill — that is a valid design decision, not a compromise.

    Finally, automate the regression. A contrast check that only runs when a designer remembers is not a check. Export your tokens as data, loop the text-on-surface pairs in a test, and fail the build when a pair drops below its floor. See tokens, export and preview for the export shape that makes this easy.