Foundations

Colors

A neutral foundation, blue actions, and semantic roles for light and dark interfaces.

Use colors by their purpose. A product can change its primary color while keeping the same component structure and behavior.

Color ramps

Five primitive families, from 50 to 950. Hover or focus a shade to inspect its value. Click, Enter, or Space copies the color value. Gray is Migoy’s generated ramp, sharing the primary hue with restrained chroma.

Gray
Blue
Green
Red
Amber

Gray comes from the approved Migoy recipe; Blue, Green, Red, and Amber come from the installed Tailwind palette. Components continue to use semantic roles. Green and Amber are palette choices; success and warning roles have not yet been defined.

Gray definition

The gray ramp is generated from tokens/gray.recipe.json. Hue is read from the theme’s light-mode primary at build time. Lightness runs from 0.985 to 0.145; chroma peaks at 0.018 and tapers to zero at both ends.

t = (step − 50) / 900
L = 0.985 − 0.84 × [0.2t + 0.8(3t² − 2t³)]
C = 0.018 × sin(πt)^0.9
H = primary hue (264.376° at generation)

11 shades generated; 0 require chroma reduction to fit sRGB.

The generator checks ascending shades, descending lightness, and sRGB gamut. If a future recipe produces an out-of-gamut shade, it reduces chroma while preserving lightness and hue. These parameters are design choices, not a contrast guarantee.

The theme defines --gray-50 through --gray-950, with matching Tailwind gray-* utilities. The installable theme includes these primitive values, aliases, and semantic references together. The swatches above read the same generated data. Changing a primitive variable updates CSS references to it; changing the recipe or primary hue requires regeneration:

pnpm palette:build
pnpm palette:test

Palette generation also runs before development and production builds.

White and black

White and black are separate primitives, not extra gray stops. They remain constant across themes; semantic roles choose which one to use.

:root {
  --white: oklch(1 0 0);
  --black: oklch(0 0 0);
}

Use bg-background for the theme-aware page surface. bg-white and bg-black reference the explicit primitives when an element needs that fixed color in both themes. Gray 50 and Gray 950 remain near-white and near-black, rather than changing the ramp endpoints.

Live semantic mapping

Neutral roles now reference the generated gray variables directly. The value shown beside each swatch below is the actual reference; copying it preserves the relationship.

:root {
  --background: var(--white);
  --card: var(--gray-50);
  --popover: var(--gray-50);
  --muted: var(--gray-100);
  --muted-foreground: var(--gray-600);
  --border: var(--gray-200);
  --input: var(--gray-400);
  --foreground: var(--gray-950);
}

.dark {
  --background: var(--black);
  --card: var(--gray-800);
  --muted: var(--gray-800);
  --muted-foreground: var(--gray-400);
  --foreground: var(--gray-50);
}

Change a primitive at the theme scope and its semantic references and components follow through CSS. No component source needs regeneration. Changing the recipe or source primary requires a build to generate new primitives. Products adopt an updated theme explicitly; the registry does not push runtime changes into installed projects.

Page backgrounds use the explicit white and black primitives: --background: var(--white) in light mode and var(--black) in dark mode. Light cards and popovers keep Gray 50 with Gray 950 text; dark cards and popovers keep Gray 800 with Gray 50 text. Blue action fills, red destructive fills, link colors, and focus colors retain their current values. Neutral surfaces, text, borders, chart shades, and interaction states now use the gray ramp. Dark secondary actions use white labels to keep contrast when pressed; dark sidebar primary labels use Gray 950 against the existing brighter blue fill.

Surfaces and text

Use background and foreground for the page, card for grouped content, and popover for floating surfaces. Pair each surface with its matching foreground. Use muted for subdued surfaces and muted-foreground for supporting text.

TokenLightDark
background
var(--white)
var(--black)
foreground
var(--gray-950)
var(--gray-50)
card
var(--gray-50)
var(--gray-800)
card-foreground
var(--gray-950)
var(--gray-50)
popover
var(--gray-50)
var(--gray-800)
popover-foreground
var(--gray-950)
var(--gray-50)
muted
var(--gray-100)
var(--gray-800)
muted-foreground
var(--gray-600)
var(--gray-400)

Actions and emphasis

Use primary for the main action, secondary for subdued filled actions, and accent for highlighted items. Pair filled actions with their matching foreground. Reserve destructive for actions that remove or destroy data. Use link for readable link text, particularly in dark mode.

TokenLightDark
primary
oklch(0.488 0.243 264.376)
oklch(0.424 0.199 265.638)
primary-foreground
oklch(0.97 0.014 254.604)
oklch(0.97 0.014 254.604)
secondary
var(--gray-100)
var(--gray-700)
secondary-foreground
var(--gray-950)
white
accent
var(--gray-100)
var(--gray-700)
accent-foreground
var(--gray-950)
var(--gray-50)
destructive
oklch(0.577 0.245 27.325)
oklch(0.704 0.191 22.216)
destructive-foreground
white
var(--background)
link
var(--primary)
#93c5fd

Borders and focus

border separates surfaces and is intentionally subtle. input is the border token for Input, Textarea, Input Group, and Combobox editing surfaces. control-border uses Gray 300 in both themes for outline buttons and other compact controls, independently of the text-entry border.

These trial shades fall below our 3:1 control-border target on the supported surfaces; see the contrast results below. Button uses focus-indicator for its keyboard outline. ring remains the general ring token; it is distinct from the Button focus indicator.

TokenLightDark
border
var(--gray-200)
var(--gray-700)
input
var(--gray-400)
var(--gray-600)
ring
var(--gray-500)
var(--gray-400)
control-border
var(--gray-300)
var(--gray-300)
focus-indicator
var(--primary)
#93c5fd

Interaction states

Neutral hover and pressed colors reference named gray steps. Primary and destructive states keep their existing color-mix formulas. Updating a referenced primitive updates the affected state. Disabled buttons use opacity rather than a separate color token.

TokenLightDark
primary-hover
color-mix(in oklch, var(--primary), black 10%)
color-mix(in oklch, var(--primary), white 10%)
primary-pressed
color-mix(in oklch, var(--primary), black 20%)
color-mix(in oklch, var(--primary), white 15%)
secondary-hover
var(--gray-200)
var(--gray-600)
secondary-pressed
var(--gray-300)
var(--gray-500)
accent-hover
var(--gray-100)
var(--gray-700)
accent-pressed
var(--gray-200)
var(--gray-600)
destructive-hover
color-mix(in oklch, var(--destructive), black 10%)
color-mix(in oklch, var(--destructive), white 10%)
destructive-pressed
color-mix(in oklch, var(--destructive), black 20%)
color-mix(in oklch, var(--destructive), white 15%)

See Button interaction states for the colors applied to real controls.

These roles belong to product navigation. They allow a sidebar to have its own surface, selected items, and focus treatment. The documentation navigation uses Fumadocs' separate theme.

TokenLightDark
sidebar
var(--gray-50)
var(--gray-800)
sidebar-foreground
var(--gray-950)
var(--gray-50)
sidebar-primary
oklch(0.546 0.245 262.881)
oklch(0.623 0.214 259.815)
sidebar-primary-foreground
var(--gray-50)
var(--gray-950)
sidebar-accent
var(--gray-100)
var(--gray-700)
sidebar-accent-foreground
var(--gray-950)
var(--gray-50)
sidebar-border
var(--gray-200)
var(--gray-700)
sidebar-ring
var(--gray-500)
var(--gray-400)

Charts

The current chart palette is monochrome. Pair series with labels, shapes, or line patterns; do not rely on these shades alone to distinguish data. Chart contrast and series distinguishability still need validation before use in production charts.

TokenLightDark
chart-1
var(--gray-200)
var(--gray-200)
chart-2
var(--gray-500)
var(--gray-500)
chart-3
var(--gray-600)
var(--gray-600)
chart-4
var(--gray-700)
var(--gray-700)
chart-5
var(--gray-800)
var(--gray-800)

Contrast checks

ThemeLowest checked text pairLowest checked control pair
Light4.76:1
destructive-foreground / destructive
1.65:1Below target
control-border / muted
Dark4.56:1
secondary-foreground / secondary-pressed
2.18:1Below target
input / card

The current checks cover text on its matching surfaces, supporting text on common neutral surfaces, action labels across neutral states, and input borders and focus colors on page, card, muted, and sidebar surfaces. Text pairs are checked at 4.5:1 and control/focus pairs at 3:1 using unrounded ratios. See text contrast and non-text contrast.

These are token-pair checks using sRGB luminance, not a complete accessibility audit. They exclude disabled opacity, decorative separators, arbitrary token combinations, and chart series. Existing blue/red colors outside sRGB are bounded to sRGB for this calculation; rendered components still need visual review. The chart palette still needs its own validation.

Using tokens

Prefer semantic utilities over literal values:

<section className="bg-card text-card-foreground border border-border">
  <h2>Project settings</h2>
  <p className="text-muted-foreground">Manage your project preferences.</p>
</section>

In CSS, reference the variable rather than copying a fixed color:

.project-surface {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
}

Semantic values appear beside their swatches. Use the copy buttons to copy light and dark CSS declarations. Values are read directly from the registry theme, including inherited dark values. Transparent colors are shown over their corresponding page background; interaction formulas resolve within each theme.

The theme primarily uses OKLCH, with derived color-mix() values and a few explicit colors for control contrast. Never communicate status using color alone. Check contrast in the actual component and state; this page is a token reference, not a guarantee that every token pairing is accessible.

To adopt the palette in a product, follow the optional theme installation. Components can also use your product's existing theme.

On this page