---
title: Color
description: The color roles every theme fills, how they're solved in OKLCH, and the contrast targets they meet.
---

Every theme fills the same roles. Components only ever read roles, so they never branch on a theme.

| Group | Roles |
| --- | --- |
| Surfaces | `surface-lower`, `surface-low`, `surface-default`, `surface-raised`, `surface-overlay` |
| Borders | `border-quiet`, `border-normal`, `border-loud`, `border-accent` |
| Text | `text-strong`, `text-primary`, `text-secondary`, `text-disabled`, `link` |
| Neutral fills | `fill-neutral-resting`, `-hovered`, `-pressed`, `-selected`, `-disabled` |
| Primary fills | `fill-primary-resting`, `-hovered`, `-pressed`, `on-primary` |
| Accent | `accent`, `fill-accent-resting`, `-hovered`, `-pressed`, `fill-accent-quiet`, `on-accent`, `focus` |
| Status | `info`, `success`, `important`, `warning`, `danger`, each with `fill-*-quiet` and `border-*` |

Each is a CSS variable named `--gw-color-<role>`.

## Surface layers

The surfaces are layers, each a step lighter than the one below in both modes. Pick a surface by what a piece is, not by how it should look:

| Layer | Role | For |
| --- | --- | --- |
| Wells | `surface-lower` | What's set into a surface: field inputs, meter and gauge tracks, segmented tracks |
| Low | `surface-low` | Quiet bands within the page, such as a sidebar |
| Page | `surface-default` | The page and the app bar |
| Raised | `surface-raised` | Cards, expansions, notifications and badges |
| Overlay | `surface-overlay` | What floats above the page: dialogs, sheets, popovers, menus, toasts and the command menu |

A small piece sitting on a surface, such as a key hint, takes a layer above it so it reads as lifted: `surface-raised` on a well or the page, and the overlay with 8% of `text-primary` mixed in on an overlay, where there's no layer left.

## Solved, not hand-set

A core theme has two inputs: a dark background and an accent. Everything else is solved in OKLCH, with derived colors mixed in OKLab:

- Surfaces keep the background's hue and chroma, stepping lightness. Light mode carries the same tint at lower chroma.
- Text is solved along the surface hue until it reaches its contrast target.
- The accent keeps its hue and chroma and moves in lightness until it reaches 5:1.
- Status colors start from GitHub's Markdown alert colors and are solved the same way.

## Contrast targets

All measured on the raised surface, the lowest-contrast surface text sits on:

| Role | Target |
| --- | --- |
| Strong text | 11:1 |
| Body text | 9:1 |
| Muted text | 4.6:1 |
| Accent | 5:1 |
| Status colors | 4.8:1 |
| Text on the accent fill | 4.5:1 |

`mise run check` fails when a core theme misses one. The [contrast report](/themes/contrast) lists every ratio.

## Status colors

GitHub's Markdown alert set, so each has its own color, mapped onto the feedback roles and onto ANSI colors in terminals:

| Alert | Role | ANSI |
| --- | --- | --- |
| Note | `info` | Blue |
| Tip | `success` | Green |
| Important | `important` | Magenta |
| Warning | `warning` | Yellow |
| Caution | `danger` | Red |

<ThemePreview theme="base" compact={false} />
