Skip to content

Theming

Make your own theme in the builder, the tokens behind it, dark mode, and several themes on one site.

Every component reads its look from CSS variables: the standard shadcn/ui tokens plus a few kit tokens. A theme is a set of values for those variables. Change them and everything restyles at once, down to corner radius, fonts and heading weight.

There are no preset themes to install. Without any changes, you get a neutral default with a blue accent. To make it yours, use the theme builder.

The theme builder

Start from one of a few starting points (Default, Editorial, Swiss, Studio, Terminal), then pick an accent, neutrals, button style, heading and body fonts, radius, shadows and borders. The builder works out both palettes, light and dark, and keeps text readable (WCAG contrast) whatever you choose. While you work, this whole site and every live preview switch to your theme, so you can browse the kit in it. Previews show components or whole templates.

Shuffle picks a random combination to explore from. Undo and Redo (⌘Z / ⇧⌘Z) step through your changes; dragging a slider counts as one step. Reset goes back to the default.

When you like it, the builder gives you three ways to use it:

  • CSS: replace the :root and .dark blocks in your global stylesheet with the generated ones.
  • shadcn CLI: download theme.json, then run npx shadcn@latest add ./theme.json. The CLI writes the variables into your CSS.
  • Prompt: paste the instructions into your coding agent.

The builder also lists the font packages to install (self-hosted with @fontsource) and the import lines to add. Copy link saves the theme in a URL, for example /themes?a=38&n=paper&h=instrument-serif. /themes.md lists every parameter.

Dark mode

Dark palettes use the .dark class, like shadcn/ui. ThemeToggle manages it (light, dark or system) and remembers the choice. To force a dark surface inside a light page (a terminal window, a footer), add className="dark" to that element.

Several themes on one site

Your main theme lives on :root. To let visitors switch between looks, scope the others with a data-theme attribute and use the ThemePicker component. A scoped theme only needs the variables it changes:

[data-theme="paper"] {
  --background: oklch(0.975 0.01 80);
  --foreground: oklch(0.2 0.02 60);
  --highlight: oklch(0.55 0.13 38);
  --highlight-ink: oklch(0.55 0.13 38);
  --font-heading: "Instrument Serif", serif;
  --heading-weight: 400;
}
<ThemePicker themes={[{ value: "default", label: "Default" }, { value: "paper", label: "Paper" }]} storageKey="theme" />

Tokens

The shadcn/ui tokens (background, foreground, card, muted, primary, border, ring…) plus:

TokenUsed for
--highlight / --highlight-foregroundThe accent as a fill: dots, active pills, badges, selection.
--highlight-inkThe accent as text on the page background (index numbers, hover links). Kept at 4.5:1 or more.
--successAvailability dots and positive states.
--card-shadowCard elevation: none, soft, or a hard offset shadow.
--font-body, --font-heading, --font-codeFont families.
--heading-weight, --heading-trackingHeading style, applied by the font-heading utility.
--display-scaleOptional multiplier for display-size headings (default 1): typography.display, HeroStatementHeading and the templates' hero titles. The builder sets 0.8 for monospace heading fonts, whose wider letters overflow the default scale.
--radiusBase radius. rounded-pill scales with it and turns square at 0.

In Tailwind they're available as bg-highlight, text-highlight-ink, border-border-hover (the border mixed toward the text color), shadow-card, font-heading and rounded-pill.

Editing by hand

If you tune the variables yourself:

  • Keep --highlight-ink at 4.5:1 or more against --background. If your highlight is a light fill (lime, yellow), make the ink a darker shade of it.
  • Keep --muted-foreground at 4.5:1 or more against --background and --card.
  • Use one heading family and one body family. Set --heading-weight to 400 for display serifs and 600–700 for grotesks.