Design system

Themes

A theme sets only its inputs: a hue for light and for dark, and optionally its two fonts and its neutrals. Every other token is core and follows from them, so every theme keeps the same shapes, spacing, states, and status colours. The family's sites each have a theme in their own hue, and open palettes such as Catppuccin and GitHub's Primer map onto the same inputs. The tests measure each theme's contrast in both modes.

Each preview renders in one theme and one mode, and its label says which. The page around the previews is Graphite Indigo unless you choose another theme below, which loads that theme's file after tokens.css, as an app would.

Graphite Indigo

Default

The default: an indigo hue on graphite neutrals.

Hue
Light #4b45d1Dark #a5a1ff
Neutrals
Core
Fonts
IBM Plex Sans for textIBM Plex Mono for code
Graphite IndigoLight
pnpm tokens:build
Graphite IndigoDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass5.34:1
Pass5.05:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass5.34:1
Pass5.05:1
Selected segmented label on its thumbNeeds 4.5:1
Pass6.61:1
Pass5.14:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass6.26:1
Pass8.31:1
The $ prompt on the command plateNeeds 3:1
Pass6.98:1
Pass7.48:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.65:1
Pass5.43:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.96:1
Pass4.29:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass6.12:1
Pass6.12:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.97:1
Pass8.31:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.76:1
Pass4.81:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.75:1
Pass7.37:1
Text and captions on the command plateNeeds 4.5:1
Pass10.29:1
Pass11.03:1

Catppuccin

Catppuccin's open palette (@catppuccin/palette 1.8.0, MIT): Latte in light and Mocha in dark, with Mauve as the hue. Base is the raised surface in both modes; Latte has nothing lighter, so base is also its page. Muted text is Subtext 1 in Latte, where Subtext 0 misses 4.5:1.

Hue
Light #8839efDark #cba6f7
Neutrals
LightDark
Fonts
Same as Graphite Indigo
CatppuccinLight
pnpm tokens:build
CatppuccinDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass3.93:1
Pass5.31:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.79:1
Pass5.31:1
Selected segmented label on its thumbNeeds 4.5:1
Pass4.79:1
Pass5.59:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass4.79:1
Pass9.23:1
The $ prompt on the command plateNeeds 3:1
Pass7.61:1
Pass8.30:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass4.71:1
Pass5.65:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.25:1
Pass4.44:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass6.72:1
Pass6.72:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.87:1
Pass8.02:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.68:1
Pass4.50:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.66:1
Pass7.18:1
Text and captions on the command plateNeeds 4.5:1
Pass9.92:1
Pass10.82:1

Copper

The devps site's orange, a step darker in light so a selected row reads at 4.5:1: a burnt orange in light and a soft apricot in dark.

Hue
Light #b04003Dark #f39a62
Neutrals
Same as Graphite Indigo
Fonts
Same as Graphite Indigo
CopperLight
pnpm tokens:build
CopperDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass4.60:1
Pass5.30:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.60:1
Pass5.30:1
Selected segmented label on its thumbNeeds 4.5:1
Pass5.67:1
Pass5.41:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass5.37:1
Pass8.74:1
The $ prompt on the command plateNeeds 3:1
Pass7.17:1
Pass7.82:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.65:1
Pass5.43:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.96:1
Pass4.29:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass6.41:1
Pass6.41:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.97:1
Pass8.31:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.76:1
Pass4.81:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.75:1
Pass7.37:1
Text and captions on the command plateNeeds 4.5:1
Pass10.06:1
Pass10.96:1

Fern

The wtree site's green, a step darker in light so a selected row reads at 4.5:1: a leaf green in light and a pale green in dark.

Hue
Light #27752aDark #7fd083
Neutrals
Same as Graphite Indigo
Fonts
Same as Graphite Indigo
FernLight
pnpm tokens:build
FernDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass4.53:1
Pass5.89:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.56:1
Pass5.89:1
Selected segmented label on its thumbNeeds 4.5:1
Pass5.54:1
Pass6.33:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass5.25:1
Pass10.23:1
The $ prompt on the command plateNeeds 3:1
Pass8.24:1
Pass8.92:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.65:1
Pass5.43:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.96:1
Pass4.29:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass7.17:1
Pass7.17:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.97:1
Pass8.31:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.76:1
Pass4.81:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.75:1
Pass7.37:1
Text and captions on the command plateNeeds 4.5:1
Pass9.86:1
Pass10.68:1

GitHub

GitHub's open Primer palette and font stacks (@primer/primitives 11.10.0, MIT), light and dark. The hue is GitHub's green: the primary button in light, and the success foreground in dark, since this system's dark buttons take a dark label. The page is Primer's default background, and surfaces its muted one.

Hue
Light #1f883dDark #3fb950
Neutrals
LightDark
Fonts
Mona Sans VF for textui-monospace for code
GitHubLight
pnpm tokens:build
GitHubDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass3.78:1
Pass4.77:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.52:1
Pass4.84:1
Selected segmented label on its thumbNeeds 4.5:1
Pass4.52:1
Pass4.77:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass4.52:1
Pass7.45:1
The $ prompt on the command plateNeeds 3:1
Pass5.76:1
Pass6.89:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.18:1
Pass4.82:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.24:1
Pass3.26:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass5.71:1
Pass5.71:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass6.70:1
Pass8.47:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass5.35:1
Pass4.91:1
Syntax colours on a code blockNeeds 4.5:1
Pass5.33:1
Pass7.08:1
Text and captions on the command plateNeeds 4.5:1
Pass9.38:1
Pass11.23:1

Iris

The filip-stack site's violet: a saturated violet in light and a lilac in dark.

Hue
Light #6d3fd6Dark #b49cff
Neutrals
Same as Graphite Indigo
Fonts
Same as Graphite Indigo
IrisLight
pnpm tokens:build
IrisDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass4.94:1
Pass5.06:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.94:1
Pass5.06:1
Selected segmented label on its thumbNeeds 4.5:1
Pass6.09:1
Pass5.14:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass5.77:1
Pass8.31:1
The $ prompt on the command plateNeeds 3:1
Pass6.88:1
Pass7.48:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.65:1
Pass5.43:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.96:1
Pass4.29:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass6.13:1
Pass6.13:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.97:1
Pass8.31:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.76:1
Pass4.81:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.75:1
Pass7.37:1
Text and captions on the command plateNeeds 4.5:1
Pass10.14:1
Pass11.03:1

Lagoon

The annoterm site's teal, a step darker in light so a selected row reads at 4.5:1: a deep teal in light and a soft aqua in dark.

Hue
Light #087266Dark #5cc8b8
Neutrals
Same as Graphite Indigo
Fonts
Same as Graphite Indigo
LagoonLight
pnpm tokens:build
LagoonDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass4.60:1
Pass5.57:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.61:1
Pass5.57:1
Selected segmented label on its thumbNeeds 4.5:1
Pass5.63:1
Pass5.86:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass5.33:1
Pass9.46:1
The $ prompt on the command plateNeeds 3:1
Pass7.67:1
Pass8.36:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.65:1
Pass5.43:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.96:1
Pass4.29:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass6.77:1
Pass6.77:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.97:1
Pass8.31:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.76:1
Pass4.81:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.75:1
Pass7.37:1
Text and captions on the command plateNeeds 4.5:1
Pass9.94:1
Pass10.83:1

Lapis

The Workbench site's blue: a cobalt in light and a periwinkle in dark.

Hue
Light #2b59d6Dark #8aa8ff
Neutrals
Same as Graphite Indigo
Fonts
Same as Graphite Indigo
LapisLight
pnpm tokens:build
LapisDark
pnpm tokens:build

Contrast

CheckLightDark
Focus ring on the page, a surface, a raised surface, and the fills an inset ring sits onNeeds 3:1
Pass4.73:1
Pass5.04:1
Current row text on its fill, on each surface, and a current link on the pageNeeds 4.5:1
Pass4.73:1
Pass5.04:1
Selected segmented label on its thumbNeeds 4.5:1
Pass5.80:1
Pass5.14:1
Primary label on its fill, at rest and on hoverNeeds 4.5:1
Pass5.49:1
Pass8.30:1
The $ prompt on the command plateNeeds 3:1
Pass6.78:1
Pass7.47:1
Text and muted text on every fill that carries them, at rest, on hover, and pressedNeeds 4.5:1
Pass5.65:1
Pass5.43:1
Control edge on the page, a surface, and a raised surfaceNeeds 3:1
Pass3.96:1
Pass4.29:1
Highlighted text in a terminal capture, and its current tabNeeds 4.5:1
Pass6.12:1
Pass6.12:1
Link text on the page, a surface, and a raised surfaceNeeds 4.5:1
Pass5.97:1
Pass8.31:1
Status text on the page, a surface, and a raised surface, on its badge, and on a danger buttonNeeds 4.5:1
Pass4.76:1
Pass4.81:1
Syntax colours on a code blockNeeds 4.5:1
Pass4.75:1
Pass7.37:1
Text and captions on the command plateNeeds 4.5:1
Pass10.02:1
Pass11.03:1

Use a theme

tokens.css carries the default theme. To use another, load its theme file after tokens.css. It sets the theme's inputs on :root, with dark blocks for the values that differ in dark, so light and dark both follow.

styles.css
@import "@filipgutica/design-system/tokens.css";@import "@filipgutica/design-system/themes/lapis.css";

Load a theme on :root only. The tokens that follow the hue resolve there, so a theme scoped to an element below :root changes the hue there, but the accent, focus ring, and selection keep the default theme's colours.

Each theme's source also ships as themes/<id>.tokens.json, a W3C Design Tokens file, for design-token tools such as Style Dictionary and Tokens Studio.

Import a VS Code theme

@filipgutica/design-system/theme-mapper is experimental. mapVsCodeTheme maps a parsed VS Code colour theme, with its include already merged, onto theme inputs. It selects the theme's own colours and never alters one. problems lists each check that still fails: a theme problem is in the theme's colours, and a core problem is a core colour, such as a link, status, or syntax colour, on the theme's fills, which the theme cannot fix. Each problem lists the inks and fills that fail.

A VS Code theme has one mode, so lock the page to it: set the variables from themeVariables inline on <html>, set data-theme to the theme's appearance, and lock the mode toggle while the theme is on. A :root rule would lose to the dark blocks in tokens.css.

theme.js
import { mapVsCodeTheme, themeVariables } from "@filipgutica/design-system/theme-mapper";const mapped = mapVsCodeTheme(vsCodeTheme);const root = document.documentElement;root.dataset.theme = mapped.appearance;for (const [name, value] of Object.entries(themeVariables(mapped))) root.style.setProperty(name, value);

Make a theme

Add a .tokens.json file to tokens/themes/ with only the inputs, then build. A theme file uses the W3C Design Tokens format (2025.10): a light and a dark group of sRGB colour tokens, a $description for its note, and its name under $extensions. A theme sets a hue for light and dark, and may name its own font stacks and its own neutrals: the page, surfaces, hover, edges, and text, for both modes. That is how an open palette, such as Catppuccin or GitHub's Primer, becomes a theme. Everything else is core. Where the hue cannot carry the text on a selected fill, the build takes the theme's text colour, then black or white. The tests check every theme against the contrast checks above, in both modes.

tokens/themes/lapis.tokens.json
{  "$description": "The Workbench site's blue: a cobalt in light and a periwinkle in dark.",  "$extensions": {    "io.github.filipgutica.design-system": {      "name": "Lapis"    }  },  "light": {    "$type": "color",    "color-project": {      "$value": {        "colorSpace": "srgb",        "components": [0.1686, 0.349, 0.8392],        "hex": "#2b59d6"      }    }  },  "dark": {    "$type": "color",    "color-project": {      "$value": {        "colorSpace": "srgb",        "components": [0.5412, 0.6588, 1],        "hex": "#8aa8ff"      }    }  }}
pnpm tokens:build