Plinth

Theming

The three token layers and how to rebrand the whole system with one file.

Plinth's entire look lives in one stylesheet: plinth.css. It's organized in three layers, and the layer boundaries are the theming contract.

Layer 1: Primitives

Raw scales with no opinion about usage: two 12-step color ramps (--gray-*, --accent-*), status color sets, radius steps, shadows, motion durations and easings, and the type scale.

The ramps follow the 12-step convention (popularized by Radix Colors), where the step number tells you what the color is for:

StepsUse
1–2App and subtle backgrounds
3–5Component fills: rest, hover, active
6–8Borders: subtle, default, strong
9–10Solid fills: rest, hover
11–12Text: secondary, primary

Dark mode is a re-declaration of the same primitive variables under the .dark class. Because everything above resolves through them, the whole system flips with no component-level dark styles.

Layer 2: Semantic

Role-based aliases that components actually use: --background, --surface, --muted-foreground, --border-strong, --primary, --ring, and so on. Each resolves to a primitive. This is the vocabulary of the system; see them rendered on the Colors page.

Layer 3: Component

Per-component hooks (like --button-height-sm) that exist only where a component genuinely needs an override point. These live next to each component, not in the global sheet.

Making your own theme

Override Layer 1 in a stylesheet loaded after plinth.css:

/* acme-theme.css */
:root {
  --accent-9: oklch(0.55 0.19 145); /* brand green */
  --accent-10: oklch(0.5 0.19 145);
  --radius-md: 0.75rem; /* rounder personality */
}

Rules of thumb:

  • Prefer overriding primitives (ramps, radius, shadows). Semantic roles keep working automatically.
  • Override semantic tokens only to change relationships, e.g. making --primary your brand color instead of high-contrast neutral.
  • Never hardcode values in components. If you need a new knob, add a Layer 3 token.

Fonts

Plinth doesn't ship a font. It reads two hook variables with system fallbacks:

:root {
  --plinth-font-sans: "Inter", sans-serif;
  --plinth-font-mono: "JetBrains Mono", monospace;
}

Set them to whatever you load (next/font, Fontsource, self-hosted).

On this page