---
title: "Check UI contrast"
url: https://recursiveai.org/onegame/cookbook/ui-contrast/
description: "Check a game UI theme against WCAG 2.x AA contrast from the command line or a test, read which token pairs fail, and pick an accent that passes."
version: "1.2.0"
---

# Check UI contrast

Check a game UI theme against WCAG 2.x AA contrast from the command line or a test, read which token pairs fail, and pick an accent that passes.

The component kit is made of tokens: `--kit-bg`, `--kit-panel`, `--kit-text`, `--kit-accent` and so on. `KitTheme.Create` builds the kit’s sheet from them and moves any text color that would fall short of AA just far enough toward white or black. Colors that aren’t text, such as your accent against the panels, it only checks. `onegame lint-theme` runs the game headless and reports every pair.

## A failing accent

The market stall from [Build a game screen with the UI kit](https://recursiveai.org/onegame/cookbook/game-ui-screen/), with a darker green accent:

```csharp
_kit.SetTheme(KitTheme.Create(KitTokens.Dark with { Accent = Color.Hex("#2F6B4F"), Radius = Length.Ui(6) }));
```

```shell
onegame lint-theme
```

The output is one JSON object, worst failure first (cut here after the failures):

```text
{"passed":false,"checked":45,"failures":[{"name":"accent fill on panel","foreground":"--kit-accent","background":"--kit-panel","foregroundColor":"#2f6b4f","backgroundColor":"#1e2128","ratio":2.56,"required":3,"passed":false,"kind":"indicator"},{"name":"accent fill on field","foreground":"--kit-accent","background":"--kit-field","foregroundColor":"#2f6b4f","backgroundColor":"#171a20","ratio":2.77,"required":3,"passed":false,"kind":"indicator"},{"name":"accent fill on bg","foreground":"--kit-accent","background":"--kit-bg","foregroundColor":"#2f6b4f","backgroundColor":"#14161a","ratio":2.88,"required":3,"passed":false,"kind":"indicator"}],"checks":[...]}
```

It exits 1. The white text on the button passes (6.29:1), but the accent fill itself is a control indicator, which needs 3:1 against the surfaces around it, and this green reaches only 2.56:1 on the panel.

## An accent that passes

A lighter green fixes the fill, but too light and the white button text fails instead. With `#3F8F69` the lint reports `primary button text on accent` at 3.93:1, under the 4.5:1 that text needs. `#367D5B` passes both:

```csharp
_kit.SetTheme(KitTheme.Create(KitTokens.Dark with { Accent = Color.Hex("#367D5B"), Radius = Length.Ui(6) }));
```

```text
{"passed":true,"checked":45,"failures":[],"checks":[...]}
```

The pairs that moved: white text on the accent 4.95:1 (needs 4.5), the accent fill on the panel 3.25:1 and on the background 3.66:1 (need 3). `onegame lint-theme` exits 0, so it can gate CI.

## The same check from C#

```csharp
var report = ThemeLint.Check(ui);             // the tokens in effect on the root; ThemeLint.Check(KitTokens) checks a token set before you use it
if (!report.Passed)
{
    foreach (var f in report.Failures)         // worst first, e.g. "primary button text on accent: --kit-accent-text #d9a441 on --kit-accent, 1.0:1, needs 4.5"
    {
        Console.WriteLine($"{f.Name}: {f.Foreground} {f.ForegroundColor} on {f.Background} {f.BackgroundColor} = {f.Ratio}:1, needs {f.Required}");
    }
}
```

This snippet is from [the UI guide](https://recursiveai.org/onegame/docs/ui-guide/). Live control has the same check as the `theme_lint` tool.

## Good to know

- The ratios checked: text 4.5:1, including text on a switched-on toggle; focus ring, control outlines, accent fills and scroll thumbs 3:1; disabled text 3:1.
- A token you override by hand after the kit’s sheet skips the repair, which is exactly what the lint is for.
- States differ by more than color: hover and pressed change the fill, focus adds a ring outside the control, and disabled controls are flat and dim in every variant.
- The lint checks the theme’s tokens, not every pixel of your own art. Look at a capture as well: see [Screenshot tests](https://recursiveai.org/onegame/cookbook/screenshot-tests/).
