Skip to content

Theming

Components are styled with semantic color tokens, not fixed colors. Change the tokens and every component follows, in light and dark.

Approach

  • The token names are the standard shadcn set. A project that already has a shadcn theme needs no changes.
  • Neutrals carry a slight warm tint, like paper and ink, so surfaces do not look blue-grey next to photographs.
  • There is one accent, a vermilion. It is used for the focus ring and the first data series (ring and chart-1) and almost nowhere else.
  • Depth comes from hairline borders first. Shadows are reserved for surfaces that float above the page.
  • Corner radii derive from a single --radius value of 0.5rem.

Tokens

TokenLightDarkUsed for
--backgroundoklch(0.985 0.003 85)oklch(0.135 0.003 70)Page background
--foregroundoklch(0.21 0.006 70)oklch(0.95 0.004 85)Default text
--cardoklch(1 0 0)oklch(0.165 0.003 70)Raised surfaces such as cards and code blocks
--card-foregroundoklch(0.21 0.006 70)oklch(0.95 0.004 85)Text on cards
--popoveroklch(1 0 0)oklch(0.185 0.003 70)Floating surfaces: menus, dialogs, toasts
--popover-foregroundoklch(0.21 0.006 70)oklch(0.95 0.004 85)Text on floating surfaces
--primaryoklch(0.23 0.006 70)oklch(0.95 0.004 85)Primary buttons, selected states, tooltips
--primary-foregroundoklch(0.985 0.003 85)oklch(0.17 0.005 70)Text on primary
--secondaryoklch(0.955 0.004 85)oklch(0.22 0.003 70)Secondary buttons and neutral badges
--secondary-foregroundoklch(0.23 0.006 70)oklch(0.95 0.004 85)Text on secondary
--mutedoklch(0.96 0.004 85)oklch(0.21 0.003 70)Tracks, skeletons and quiet fills
--muted-foregroundoklch(0.47 0.008 70)oklch(0.72 0.006 80)Secondary text
--accentoklch(0.945 0.005 85)oklch(0.24 0.003 70)Hover and highlighted rows
--accent-foregroundoklch(0.23 0.006 70)oklch(0.95 0.004 85)Text on accent
--destructiveoklch(0.53 0.2 27)oklch(0.7 0.18 25)Errors and destructive actions
--borderoklch(0.905 0.005 85)oklch(0.26 0.003 70)Hairline borders and dividers
--inputoklch(0.85 0.006 85)oklch(0.34 0.003 70)Borders of form controls, switch tracks
--ringoklch(0.6 0.2 35)oklch(0.7 0.17 38)Focus rings
--chart-1oklch(0.6 0.2 35)oklch(0.7 0.17 38)First data series, the accent
--chart-2oklch(0.48 0.09 250)oklch(0.7 0.1 250)Second data series
--chart-3oklch(0.7 0.13 85)oklch(0.8 0.12 85)Third data series
--chart-4oklch(0.56 0.08 150)oklch(0.72 0.1 150)Fourth data series
--chart-5oklch(0.52 0.06 330)oklch(0.72 0.07 330)Fifth data series

Changing the accent

Override the two accent tokens in your global stylesheet. Check the focus ring against both backgrounds: it needs a 3:1 contrast ratio to be visible.

app/globals.css
:root {
  --ring: oklch(0.55 0.17 255);
  --chart-1: oklch(0.55 0.17 255);
}

.dark {
  --ring: oklch(0.72 0.14 255);
  --chart-1: oklch(0.72 0.14 255);
}

Dark mode

Dark values apply under a .dark class on any ancestor, usually html. Use a library such as next-themes to set it from the system preference. Dark mode is not an inversion: surfaces get lighter as they rise, and the accent is lightened so it keeps its contrast.

app/globals.css
@custom-variant dark (&:is(.dark *));

Radius and type

Set --radius to change every corner at once. Components inherit the font from the page and use font-mono only for keyboard hints and code. They use tabular figures wherever numbers change in place.

:root {
  --radius: 0.375rem;
}