One of the 26 palette names. Matched case-insensitively; anything unrecognized
falls back to violet.
Theme Variants
lib/theme-variants.ts registers 26 named palettes. Each one carries a full set
of light values, a full set of dark values, and a remap of Tailwind's blue
scale, all emitted as CSS custom properties into an inline <style> in the
document head. Picking one is a single environment variable — no code, no CSS
overrides.
Quick Start
Leave the default in place and the site renders in violet.
THEME_VARIANT=violetThe variable is read once when the server starts, so restart npm run dev after
changing it.
Adding Options
Every name below is a valid value. Names are matched lowercase, so AMBER and
amber both land on the same palette.
emerald
Green-leaning OKLCH scale at hue 162, distinct from green and teal.
amber
Own OKLCH scale of 11 stops at hue 72.
blue
Own OKLCH scale at hue 250 — an actual blue.
cyan
Own OKLCH scale.
fuchsia
OKLCH scale shared with pink.
green
Own OKLCH scale at hue 148.
indigo
Own OKLCH scale.
lime
Own OKLCH scale.
mauve
Near-neutral scale with a faint magenta cast.
mist
Near-neutral scale with a faint blue cast.
neutral
Same near-neutral scale as zinc.
olive
Near-neutral scale with a faint yellow-green cast.
orange
Own OKLCH scale.
pink
Own OKLCH scale at hue 345.
purple
Own OKLCH scale at hue 308.
red
Own OKLCH scale at hue 25.
rose
Own OKLCH scale at hue 10.
sky
Own OKLCH scale, and an actual blue.
slate
Same near-neutral scale as zinc.
stone
Warm-leaning near-neutral scale.
taupe
Near-neutral scale with a faint warm brown cast.
teal
Own OKLCH scale.
violet
Default. Own OKLCH scale at hue 296 — the OwnDocs brand hue.
yellow
Own OKLCH scale at hue 95.
zinc
Cool-leaning near-neutral scale.
gray
Same near-neutral scale as zinc.
THEME_VARIANT=slateFour names resolve to one identical near-neutral scale, which is worth knowing before you spend an afternoon comparing them:
gray,neutral,slate, andzinc
Every other name has its own hue. That leaves 23 visually distinct results across the 26 names.
Advanced
Deploy platforms set the same variable, so a Render service picks its palette
from render.yaml instead of a local file. An unrecognized name falls back to
violet rather than breaking the build.
envVars:
- key: THEME_VARIANT
value: violetWhat the chosen variant produces is a single CSS string, formatted here for reading and trimmed to a few properties:
:root {
--color-bg-primary: #fcfefd;
--color-accent: oklch(59.6% 0.145 163.225);
--color-blue-500: oklch(69.6% 0.17 162.48);
}
.dark {
--color-bg-primary: oklch(26.2% 0.051 172.552);
--color-accent: oklch(76.5% 0.177 163.223);
}The generated themes map palette stops to roles the same way every time: light mode takes its surfaces from stops 50 and 100, borders from 200 and 300, and the accent from 600 with 700 on hover. Dark mode takes surfaces from 950, 900, and 800, borders from 800 and 700, and the accent from 400 with 300 on hover. Emerald is the exception — its light and dark values are written out by hand, and only its blue-scale remap comes from a palette.
Two more things ride on the same setting:
- The dark-mode toggle is separate from the palette. Readers pick light or dark
themselves, it's stored in
localStorageunderdocs-theme-mode, and it flips the.darkclass on<html>. The variant supplies the colors for both sides of that switch. - Open Graph images read the same variant.
app/og/route.tsxpulls--color-bg-primary,--color-text-primary,--color-text-tertiary,--color-accent, and--color-border-primarystraight off it, so social cards match the site.
Components never reference a palette directly. They use the semantic aliases —
surface-primary, surface-secondary, surface-tertiary, content-primary,
content-secondary, content-tertiary, edge-primary, edge-secondary, and
brand — which resolve to whichever variant is loaded.
Options
THEME_VARIANTenvDefault: violetAVAILABLE_THEMESstring[]The 26 registered names, exported from lib/theme-variants.ts for anything
that needs to list or validate them.
DEFAULT_THEMEstringDefault: violetThe fallback used when THEME_VARIANT is unset or unknown.
getThemeVariant(name?: string) => ThemeVariantResolves a name to its variant, lowercasing first and falling back to the default. Used by both the root layout and the Open Graph image route.
generateThemeCss(theme: ThemeVariant) => stringSerializes a variant into the :root { ... } .dark { ... } string injected
into the document head.
lightRecord<string, string>The 16 light-mode custom properties on a variant, including
--color-api-code-bg.
darkRecord<string, string>The dark-mode custom properties, matching light key for key.
blueRecord<string, string>Eleven --color-blue-* overrides that repoint Tailwind's blue scale at the
variant's own stops, so existing blue utility classes follow the theme.