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

Source: https://saer.app/guides/check-colour-contrast-light-dark-mode
Updated: 2026-10-22

**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](https://saer.app/design#colour) 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 built yet

- Rendered-page testing: Saer's checks are static, so they are not a statement of WCAG conformance.
