---
title: Colors
description: Neutral greys carry the interface. Cobalt marks the one thing to act on; amber and red mark state.
---

Token names read as property, role, level: `bg-control-hover`, `fg-muted`, `accent-emphasis`. In code use the utility: `bg-*` for surfaces, `text-*` for text and icons. Swatches show light and dark; text tokens show their contrast on the surface they sit on.

## Surfaces

```tsx
import { ColorTokens } from "../../src/ColorTokens";
import { SURFACE_TOKENS } from "../../src/constants";

export default function SurfacesColors() {
  return <ColorTokens tokens={SURFACE_TOKENS} />;
}
```

## Text and icons

```tsx
import { ColorTokens } from "../../src/ColorTokens";
import { TEXT_TOKENS } from "../../src/constants";

export default function TextColors() {
  return <ColorTokens tokens={TEXT_TOKENS} />;
}
```

## Accent

```tsx
import { ColorTokens } from "../../src/ColorTokens";
import { ACCENT_TOKENS } from "../../src/constants";

export default function AccentColors() {
  return <ColorTokens tokens={ACCENT_TOKENS} />;
}
```

- If two things on screen are blue, one of them is wrong.
- Links and the New badge use `text-accent`, a darker shade that holds 4.5:1 on light grounds.

## State

```tsx
import { ColorTokens } from "../../src/ColorTokens";
import { STATE_TOKENS } from "../../src/constants";

export default function StateColors() {
  return <ColorTokens tokens={STATE_TOKENS} />;
}
```

- Amber for things that need attention, red for things that are broken. Green only marks a resolved status: fine is the default and needs no colour.
- State colours mark icons, badges and numbers, never whole rows or panels.

## Rings

- `ring-accent` is the focus ring of every interactive control.
- `ring-control` outlines an unchecked checkbox.
- `ring-ground` cuts a marker or a stacked avatar out of what it overlaps.

## Charts

```tsx
import { ColorTokens } from "../../src/ColorTokens";
import { CHART_TOKENS } from "../../src/constants";

export default function ChartColors() {
  return <ColorTokens tokens={CHART_TOKENS} />;
}
```

- Grey carries the data. `accent-emphasis` marks the recent 20% of the story series; warning, danger and critical mark state.
- Text in legends and tooltips is never drawn in the series colour.

## Palettes

```tsx
import { paletteIds } from "@openperf/deft";

const SAMPLES = paletteIds.flatMap((id) =>
  (["light", "dark"] as const).map((theme) => ({ id, theme })),
);

const SWATCHES = ["bg-inset", "accent-emphasis", "chart-ramp-2", "chart-ramp-3", "chart-ramp-4"];

export default function Palettes() {
  return (
    <div className="grid grid-cols-4 gap-3 font-sans text-body-md">
      {SAMPLES.map((sample) => (
        <div
          className="flex flex-col gap-2 rounded-lg bg-default p-3 text-default shadow-raised"
          data-palette={sample.id}
          data-theme={sample.theme}
          key={`${sample.id}-${sample.theme}`}
        >
          <div className="flex items-baseline justify-between gap-2">
            <code className="font-medium">{sample.id}</code>
            <span className="text-body-sm text-muted">{sample.theme}</span>
          </div>
          <div className="flex gap-1">
            {SWATCHES.map((name) => (
              <span
                className="h-4 flex-1 rounded-xs"
                key={name}
                style={{ background: `var(--${name})` }}
              />
            ))}
          </div>
          <span className="w-fit rounded-sm bg-accent px-2 py-0.5 text-body-sm font-medium text-on-accent">
            Resolve
          </span>
        </div>
      ))}
    </div>
  );
}
```

- A palette rewrites every colour token, with a light and a dark scheme. `data-palette` picks the palette and `data-theme` (`light`, `dark` or `system`) picks the scheme. Both work on any element, usually `<html>`. Without `data-palette`, the base colours on this page apply.
- Tokens resolve with `light-dark()`, so `system` follows the OS without a script.
- Palettes live in `packages/deft/src/libs/palettes/palettes.ts`. `palettes.gen.css` is a file snapshot of them: after changing a value, run `bun run test --project @openperf/deft -u` to regenerate it. The test fails while the two disagree.
