CSS Design Token Generator
Generate a full 50-950 OKLCH color palette plus semantic CSS custom properties — primary, surface, border, ring, and more, with an optional dark-mode variant — from a single brand hex color.
Input
Pick a color, or type a HEX value directly.
Used as the palette key, e.g. --color-brand-500.
Modern outputs oklch() values; Hex outputs classic #rrggbb.
Adds a second selector with dark-friendly semantic token overrides.
Scale palette saturation. 100 keeps the brand color's chroma.
Output
Guides
What are design tokens?
Design tokens are the named, reusable values — colors, spacing, radii, type sizes — that make up a design system. Instead of hard-coding #3b3ff0 in twenty different components, you define --color-primary once and reference it everywhere. Change the token's value and every component that uses it updates together. Tokens are what let a design system stay consistent as it grows, and what let a single dark-mode switch repaint an entire product.
Why CSS custom properties are a common target format
Design tokens can be exported to many formats — JSON, a JS/TS object, Sass variables — but CSS custom properties (--my-token: value;) have a distinct advantage: they're native to the browser, resolved at render time, and automatically cascade and override by selector. That's what makes theming possible with plain CSS: define your tokens once under :root, then override a subset of them under .dark (or @media (prefers-color-scheme: dark)), and every component that consumes var(--color-primary) repaints without any JavaScript or rebuild. This tool targets CSS custom properties specifically because that cascade-and-override behavior is exactly what a color system needs.
How this tool builds the palette
Starting from a single brand hex color, the tool interpolates through OKLCH — a perceptually-uniform color space — to generate an 11-stop scale (50 through 950), the same shape used by Tailwind's default palette. Because OKLCH lightness steps are perceptually even, each stop looks like a consistent jump in brightness, not the uneven jumps you get interpolating raw RGB or HSL. Every generated stop is also clamped back into the sRGB gamut, so it always renders as a valid, displayable color even for highly saturated brand colors.
On top of the raw palette, the tool maps a fixed set of semantic roles — --color-primary, --color-surface, --color-border, --color-ring, and others — onto specific stops (e.g. primary → 600 in light mode, 400 in dark mode). Those semantic names are what your components should actually reference; the raw 50–950 scale exists mainly to back them.
How to use this tool
- Pick a Brand Color (or type a hex value directly).
- Set a Token Name — it becomes the palette's key, e.g.
--color-brand-500. Use something specific to your brand or product, likeacmeorindigo. - Choose a Format:
oklch()for modern, wide-gamut-ready values, or plain hex for maximum compatibility with older tooling. - Leave Include dark mode variant on to get a second block of semantic overrides, and choose a Dark selector that matches your framework — a
.darkclass (Tailwind, shadcn), a[data-theme="dark"]attribute, or the@media (prefers-color-scheme: dark)query for OS-level theming without any JavaScript toggle. - Adjust Chroma to scale the palette's saturation up or down — 100 keeps the brand color's original intensity, lower values produce a more muted, desaturated scale.
- Copy the generated CSS, or download it as a
.cssfile, and drop it into your stylesheet.
The output updates instantly as you adjust any control.
FAQ
Why does the palette include both raw stops (--color-brand-600) and semantic tokens (--color-primary)?
The raw scale is the source of truth; semantic tokens are an alias layer on top of it. Components should reference the semantic names — --color-primary, --color-border — not the raw stops directly, so that redesigning the palette (or swapping brand colors) never requires touching component code.
What does the Chroma slider actually do? It scales the saturation (chroma) of every generated stop relative to your brand color's own chroma, while re-clamping each stop back into the sRGB gamut. 100 reproduces the brand color's saturation as closely as each lightness stop's gamut allows; values below 100 produce a calmer, more muted palette; values above 100 push toward the most saturated color displayable at that lightness.
Which dark-mode selector should I pick?
Use .dark if your framework (Tailwind, shadcn/ui, and most component libraries) toggles a class on <html> or <body>. Use [data-theme="dark"] if you toggle a data attribute instead. Use the @media query if you want the page to follow the operating system's theme automatically, with no toggle at all.
Can I use the oklch() values if I need to support older browsers?
oklch() is supported in all current major browsers, but if you need to support significantly older ones, switch the Format to Hex — the same palette and semantic structure is generated either way, just with #rrggbb values instead of oklch().
Privacy
Everything happens in your browser. No color, token name, or setting you enter is ever sent to a server — the CSS is generated and updated locally as you type.