Skip to content

Color Roles

Checked by text-contrast

Give the brand color to everything that could take it, and it stops meaning anything. The title is blue, the tabs are blue, the numbers, links and status badges are blue, and the one button you want people to press is just another blue thing.

Let neutrals do almost all the work. Use the accent only for the main action and the current selection, and use green, amber and red only when they mean something.

Accent16Status0

In the first card the accent is on 16 elements. "Ready", "Building" and "Failed" wear the same blue pill, so the statuses carry no meaning at all. In the second, the accent is on two: the selected tab and Deploy. Each status gets a small dot, and the failed build is the first thing you notice after the button.

This is visual hierarchy applied to color. Every colored element competes for attention, so only color the ones that should win.

Three roles

  • Neutrals are backgrounds, surfaces, lines, text and icons. They are nearly everything on screen.
  • The accent marks what you can do next and where you are: the primary button, the selected tab, a checked box. One hue.
  • Status colors report state: success, warning, danger. They never decorate. If something is red, something went wrong.

Tinted neutrals

Pure gray is chroma 0 in OKLCH. Next to a saturated accent it can look flat and unrelated. Add a trace of the brand hue to every neutral and the grays and the accent start to look like one palette.

Between 0.005 and 0.015 the grays still read as gray, just cooler, toward the indigo accent. By 0.03 the whole card has turned blue.

Not everyone tints. Radix ships plain gray, which works with any accent, next to grays pre-tinted toward each hue. Linear's redesign went the other way and cut back the blue in its neutrals. I'd tint, and stay under 0.015. This site has no brand hue, so its own grays sit at 0.

Usage

styles.css
@import "tailwindcss";

@theme {
  --color-page: oklch(0.965 0.01 265);
  --color-surface: oklch(0.99 0.005 265);
  --color-line: oklch(0.92 0.01 265);
  --color-ink: oklch(0.21 0.01 265);
  --color-ink-soft: oklch(0.52 0.01 265);
  --color-accent: oklch(0.52 0.18 265);
  --color-success: oklch(0.62 0.15 150);
  --color-warning: oklch(0.75 0.15 75);
  --color-danger: oklch(0.58 0.2 27);
}

/* <button class="bg-accent text-white">Deploy</button> */
:root {
  --hue: 265;
  --tint: 0.01;

  --page: oklch(0.965 var(--tint) var(--hue));
  --surface: oklch(0.99 calc(var(--tint) / 2) var(--hue));
  --line: oklch(0.92 var(--tint) var(--hue));
  --ink: oklch(0.21 var(--tint) var(--hue));
  --ink-soft: oklch(0.52 var(--tint) var(--hue));

  --accent: oklch(0.52 0.18 var(--hue));
  --success: oklch(0.62 0.15 150);
  --warning: oklch(0.75 0.15 75);
  --danger: oklch(0.58 0.2 27);
}

If your brand hue is red or green, it will collide with danger or success. Keep the brand for the accent, and move the status color far enough along the hue wheel, or add an icon, so nobody mistakes your logo color for an error.

Resources

Search concepts

Search for a command to run...