shadcn/ui components read their colours from CSS variables such as --primary, --muted and --border, written in OKLCH. Choose a hue, chroma, lightness, grey tint and corner radius here, check the live light and dark preview with contrast ratios, then paste the generated :root and .dark blocks into your global stylesheet.
| Surface | Text on it | Used by |
|---|---|---|
| --background | --foreground | Page body |
| --card / --popover | --card-foreground / --popover-foreground | Cards, menus, dialogs |
| --primary | --primary-foreground | Default buttons, active states |
| --secondary / --muted | --secondary-foreground / --muted-foreground | Quiet buttons, hints, captions |
| --accent | --accent-foreground | Hovered menu items |
Updated
The theming docs define about thirty variables, and editing them by hand is slow because a change to the primary colour also changes which text colour stays readable on it. This generator derives the whole set from a few choices, picks a readable foreground for each surface, and flags any pair that falls below common contrast targets.
Presets: Neutral, Blue, Violet, Green, Orange, Rose. Adjust hue, chroma, lightness, grey tint and radius, preview light and dark, and copy the CSS.
This is exactly what the generator writes for the Blue preset. Paste it over the colour blocks in globals.css.
Light values sit in :root and dark values in .dark, both in OKLCH.
:root {
--radius: 0.625rem;
--background: oklch(1 0.001 258);
--foreground: oklch(0.145 0.006 258);
--card: oklch(1 0.001 258);
--card-foreground: oklch(0.145 0.006 258);
--popover: oklch(1 0.001 258);
--popover-foreground: oklch(0.145 0.006 258);
--primary: oklch(0.55 0.2 258);
--primary-foreground: oklch(0.985 0.002 258);
--secondary: oklch(0.97 0.006 258);
--secondary-foreground: oklch(0.205 0.006 258);
--muted: oklch(0.97 0.006 258);
--muted-foreground: oklch(0.556 0.006 258);
--accent: oklch(0.96 0.04 258);
--accent-foreground: oklch(0.205 0.006 258);
--destructive: oklch(0.577 0.23 27);
--border: oklch(0.922 0.006 258);
--input: oklch(0.922 0.006 258);
--ring: oklch(0.55 0.2 258);
--chart-1: oklch(0.65 0.2 258);
--chart-2: oklch(0.65 0.2 320);
--chart-3: oklch(0.65 0.2 22);
--chart-4: oklch(0.65 0.2 84);
--chart-5: oklch(0.65 0.2 146);
--sidebar: oklch(0.985 0.003 258);
--sidebar-foreground: oklch(0.145 0.006 258);
--sidebar-primary: oklch(0.55 0.2 258);
--sidebar-primary-foreground: oklch(0.985 0.002 258);
--sidebar-accent: oklch(0.96 0.04 258);
--sidebar-accent-foreground: oklch(0.205 0.006 258);
--sidebar-border: oklch(0.922 0.006 258);
--sidebar-ring: oklch(0.55 0.2 258);
}
.dark {
--background: oklch(0.145 0.006 258);
--foreground: oklch(0.985 0.002 258);
--card: oklch(0.205 0.006 258);
--card-foreground: oklch(0.985 0.002 258);
--popover: oklch(0.205 0.006 258);
--popover-foreground: oklch(0.985 0.002 258);
--primary: oklch(0.65 0.2 258);
--primary-foreground: oklch(0.205 0.006 258);
--secondary: oklch(0.269 0.006 258);
--secondary-foreground: oklch(0.985 0.002 258);
--muted: oklch(0.269 0.006 258);
--muted-foreground: oklch(0.708 0.006 258);
--accent: oklch(0.3 0.06 258);
--accent-foreground: oklch(0.985 0.002 258);
--destructive: oklch(0.704 0.19 22);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.65 0.2 258);
--chart-1: oklch(0.7 0.2 258);
--chart-2: oklch(0.7 0.2 320);
--chart-3: oklch(0.7 0.2 22);
--chart-4: oklch(0.7 0.2 84);
--chart-5: oklch(0.7 0.2 146);
--sidebar: oklch(0.205 0.006 258);
--sidebar-foreground: oklch(0.985 0.002 258);
--sidebar-primary: oklch(0.65 0.2 258);
--sidebar-primary-foreground: oklch(0.205 0.006 258);
--sidebar-accent: oklch(0.3 0.06 258);
--sidebar-accent-foreground: oklch(0.985 0.002 258);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.65 0.2 258);
}Define the pair in both modes, then expose it to Tailwind through @theme inline.
:root {
--warning: oklch(0.82 0.15 80);
--warning-foreground: oklch(0.3 0.06 55);
}
.dark {
--warning: oklch(0.47 0.1 60);
--warning-foreground: oklch(0.98 0.02 90);
}
@theme inline {
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
}
Your primary colour feeds the primary, ring and sidebar-primary variables. Greys for backgrounds, borders and muted text share one hue with a tiny chroma set by the tint slider, so the interface feels related to the brand without looking coloured.
Each foreground is chosen by measuring contrast against its surface and keeping whichever of a near-white or near-black reads better. Chart colours step around the hue wheel from your primary hue so series stay distinguishable.
In the dark block, borders and inputs use white at low alpha instead of a solid grey, which lets them adapt to whatever surface they sit on. Cards are lifted slightly above the page background so layers stay visible.
Test focus rings in dark mode in particular. A ring that is clear on white can vanish on a deep navy page.
OKLCH lightness tracks how bright a colour looks, so two colours with equal L feel equally light even when their hues differ. That makes it easier to build matching light and dark sets. The current shadcn/ui theming docs use OKLCH for every colour.
L is lightness from 0 (black) to 1 (white), C is chroma, how vivid the colour is, starting at 0 for grey, and H is the hue angle in degrees. An optional alpha follows a slash, as in oklch(1 0 0 / 10%).
Components round their corners from this one value. The theming docs set it to 0.625rem by default, and the @theme inline block derives smaller and larger radius steps from it.
Projects on older templates may store colours in a different format and map them differently in the Tailwind config. Compare your globals.css with the variables here before replacing anything.
They use the WCAG 2 formula after converting each OKLCH colour to sRGB, clipping any out-of-range value. Colours outside sRGB will display slightly differently on wide-gamut screens.
Related: shadcn/ui examples, shadcn form and table, Tailwind colour palette generator, Colour contrast checker, Dark mode for websites, CSS variables guide.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.