Theming
Override CSS custom properties to make the design system yours. Every token resolves automatically in light and dark mode.
How theming works
Changing the accent color
The most common customization. Define both light and dark values.
:root {
--accent: oklch(0.65 0.22 150);
}
.dark {
--accent: oklch(0.72 0.19 150);
}This changes links, active states, and primary buttons across every component. Colors use the OKLCH color space for perceptual uniformity, so equal lightness steps look equally bright.
What can I theme?
Every token category can be overridden. See the design system pages for the full catalog.
| Category | Tokens |
|---|---|
| Colors | Accent, destructive, warning, success, surfaces, text hierarchy |
| Typography | 12 sizes from 3xs to jumbo, font families, weights, tracking |
| Spacing | Base unit, container width, max-width scale, breakpoints |
| Shadows | Four-level shadow scale from xs to lg |
| Radii | Component tier system: form controls, buttons, cards, modals, indicators |
Overriding multiple tokens
Override as many or as few tokens as you need.
:root {
/* Colors */
--accent: oklch(0.65 0.22 150);
--background: oklch(0.98 0.005 90);
--card: oklch(1 0 0);
/* Radius (override the scale step you want to reshape) */
--radius-lg: 0.75rem;
}
.dark {
--accent: oklch(0.72 0.19 150);
--background: oklch(0.15 0.005 90);
--card: oklch(0.18 0.005 90);
}Each component tier reads a step of the --radius-* scale: inputs use --radius-md, buttons and cards use --radius-lg, drawers and sheets use --radius-xl. Override the step you want to reshape.
Typography
Font families resolve through a three-level chain, so the base theme never ships a licensed typeface.
Default: Geist Sans and Geist Mono
Do nothing and you get Geist. create-next-app already wires this up via next/font/google, so most projects need no extra setup:
import { Geist, Geist_Mono } from "next/font/google"
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
})
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
})Preferred: Innovator Grotesk
This site renders in Innovator Grotesk, the UI.MD brand face. It’s a commercial font and isn’t part of the registry, so licensees load it themselves and wire it through the --font-sans-custom hook:
// app/layout.tsx
import localFont from "next/font/local"
const innovatorGrotesk = localFont({
src: "./fonts/InnovatorGroteskVF-VF.woff2",
variable: "--font-innovator",
weight: "100 900",
display: "swap",
}):root {
--font-sans-custom: var(--font-innovator);
}Any other font
The hook isn’t specific to Innovator Grotesk. Load any font with next/font, then point --font-sans-custom (or --font-mono-custom for code) at its variable to override the default without touching component code.