How do you check colour contrast across light and dark mode in a codebase?

Short answer

Contrast has to be checked per context. Find the text and background colours written together in the source, resolve each through its tokens in every theme and mode (default, dark, any data-theme), composite any transparency, and apply the WCAG 2.x ratio: 4.5:1 for normal text, 3:1 for large text and interface boundaries. A static check covers what the code states; it is not a conformance claim, which needs testing the rendered pages.

Why a single check is not enough

Semantic tokens change value by context. fg-muted on bg can pass on a light surface and fail on a dark one if only one side was tuned. Checking one theme in a browser misses the other. In Lunary, Saer's static check found 48 failing pairs in the default context and 111 more that fail only in another context.

This site states its pairs in tokens.json and tests each one on both surfaces on every build.

What a static check can and cannot see

  • Can: pairs written together (one element, a CSS rule, a component variant), token chains, opacity modifiers.
  • Cannot: a background set by a parent elsewhere, gradients, images, conditional classes. These should be reported as unknown, not guessed.

What Saer does about this

Today
  • Accessibility static checks: contrast per light and dark context, focus styles, target sizes, reduced motion.
Not yet
  • Rendered-page testing: Saer's checks are static, so they are not a statement of WCAG conformance.

Every capability and its state: what Saer checks.

See it on your own codebase.

Request early access