on GitHub

Theming

Every color and radius the components read is a CSS custom property, defined once in globals.css at :root (--background, --card, --primary, --chart-1…5, --radius, and the rest). A page restyles itself by dropping a layout.tsx beside its page: a default-exported Layout that wraps children in a <div style={theme}> that reassigns those variables. The override is scoped to that one route subtree, so nothing else on the site shifts.

This page ships exactly that file: a warm palette with squared corners, applied to everything below. View source to see the whole of it; in outline:

// capabilities/theming/layout.tsx
const theme = {
  "--radius": "0.25rem", // was 0.625rem; cascades into --radius-sm/md/lg/xl
  "--background": "#faf7f2",
  "--primary": "#b45309", // amber, was near-black
  "--chart-1": "#d97706", // warm ramp for every chart
  // …the rest of the token set
} as CSSProperties;

export default function Layout({ children }) {
  return (
    <div style={theme} className="bg-background text-foreground">
      {children}
    </div>
  );
}

Overridable tokens

There's no separate registry to learn: the catalog is globals.css :root. Every variable defined there is a token you can override from a layout.tsx, and the :root values are just the site defaults (each with a .dark counterpart in the same file). The full set, by role:

  • Surfaces: --background / --foreground, --card / --card-foreground, --popover / --popover-foreground
  • Semantic roles: --primary, --secondary, --muted, --accent (each with a -foreground pair), plus --destructive and --brand / --brand-foreground
  • Lines & focus: --border, --input, --ring
  • Charts: --chart-1 through --chart-5, the qualitative ramp
  • Radius: --radius, the single value from which globals.css derives --radius-sm/md/lg/xl/2xl/3xl/4xl

One set is deliberately not page-overridable: the --sidebar-* tokens style the site chrome, which lives outside any page's subtree, so reassigning them in a page layout.tsx does nothing. Theme those in globals.css itself.

The same components, retinted

None of the components below know they've been themed. They read tokens by name, so the palette above flows straight into them. Metrics and cards take --card / --foreground, the chart takes the --chart-* ramp, the button takes --primary, and every rounded corner takes --radius.

Flowers
Avg petal length
07
Longest petal
Petal shape by species
Loading…

Scoping tighter than a page

layout.tsx is only a convenience: it themes the whole page because it wraps the whole page. Underneath it's nothing but CSS custom properties on a style= object, so you can set the same tokens on any element to re-theme just the subtree it wraps. Overrides cascade, and tokens you don't set keep inheriting the theme above, so a partial override rides on top of the page palette:

<div
  style={{
    "--primary": "#0f766e",
    "--radius": "1rem",
    "--chart-1": "#0891b2",
    "--chart-2": "#0d9488",
    "--chart-3": "#0e7490",
  }}
>
  <Card>{/* the same chart, now cool-toned and rounder */}</Card>
</div>
Same data, a nested theme
Loading…

This card sits inside the page's warm theme, yet its chart marks, its corners, and any --primary control read the cooler palette set one div up, while its --card and --foreground, left unset, still inherit the warm page. Everything outside the div stays warm. layout.tsx is just this same trick applied to the whole page.

Dark mode

An inline style= override sets each variable for both light and dark at once. globals.css gives its tokens distinct .dark values, so a theme that must differ by mode puts its dark overrides under a .dark selector rather than inline here.