How to Build a Tailwind-Style Color Scale (50–950) From One Color
You picked a brand color. One hex. It looks great in the logo, and then you sit down to build an actual interface and discover you need about eleven versions of it — a whisper-pale panel background, a mid-weight button, near-black body text that still carries the tint. This is the gap nobody warns you about, and here is how to close it.
One color is never enough
A single hue can't do the work of a real UI. Buttons want a confident mid-tone. Hover states want to shift a notch darker. Cards want a background so faint you almost don't see it. Divider lines want something in between. Body text wants a color that reads as "very dark brand" rather than flat black.
That's five jobs already, and I'm just getting started. The fix teams settled on is a numbered scale: stretch your one color into eleven fixed rungs. Tailwind ships it. So do Radix and Material.
The 50–950 mental model
The rungs get names by number, not by feeling. Low numbers are light. High numbers are dark.
50— almost white, the faintest tint of your color500— usually your base brand color950— almost black, still carrying the hue
Everything else fills the gaps between them, climbing in hundreds up to 900. Why does this matter beyond tidiness? Because everyone on the team shares one vocabulary. A designer says "use brand-200 for the border" and the engineer knows exactly which token to reach for. No arguing about whether "light blue" means the pale one or the medium one. The number is the spec.
If you want the groundwork on how a color lightens and darkens in the first place — tint, shade, tone — the tints, shades and tones piece is the primer. This article picks up where that one stops and builds the production scale.
Why naive steps clump in the middle
Here's the trap. You reach for HSL, keep hue and saturation fixed, and march lightness in even jumps: 90%, 80%, 70%, down to 10%. Feels reasonable. It isn't.
HSL lightness doesn't match how your eye reads brightness. The steps near the middle look nearly identical while the ends spread apart. You get a muddy middle — 400, 500, and 600 all blur together — and mushy, low-contrast rungs right where your buttons live. Not good.
The answer is to space the scale perceptually. That's where OKLCH earns its keep. Its lightness axis tracks human perception, so an even walk down the L channel actually looks even. Same visual gap between each rung, top to bottom.
The method, in three moves
Build every rung with oklch(L C H) and follow three rules.
Hold the hue steady. Pick your H once and keep it locked across all eleven rungs. That's what makes the scale read as one family instead of eleven cousins.
Walk lightness in even perceptual steps. Start near L 0.98 for the 50 and descend toward L 0.2 or so for the 950. Even spacing in OKLCH, even spacing to the eye.
Ease chroma down at the extremes. This is the move people miss. Full chroma at the top makes your 50 and 100 look radioactive — neon pastels nobody wants behind text. Full chroma at the bottom turns your 900 and 950 muddy and dark-grimy. So you curve chroma up toward the middle rungs and pull it back at both ends. Peak saturation lands around 400–600, right where the brand color wants to sing.
A real ramp
Here's a blue built the right way. Notice lightness falling in steady steps while chroma rises into the middle and eases off at the edges.
--brand-50: oklch(0.98 0.02 250); --brand-100: oklch(0.95 0.04 250); --brand-200: oklch(0.90 0.07 250); --brand-300: oklch(0.82 0.11 250); --brand-400: oklch(0.72 0.16 250); --brand-500: oklch(0.62 0.19 250); --brand-600: oklch(0.54 0.18 250); --brand-700: oklch(0.46 0.15 250); --brand-800: oklch(0.38 0.11 250); --brand-900: oklch(0.30 0.08 250); --brand-950: oklch(0.22 0.05 250);
Hue stays pinned at 250. Lightness marches from 0.98 down to 0.22. Chroma peaks at 0.19 near the 500 and tapers to 0.02 and 0.05 at the ends. Drop those into a :root block and you have a working palette.
Wiring it into Tailwind v4 is short, because v4 moved to a CSS-first token model with @theme:
@theme {
--color-brand-50: var(--brand-50);
--color-brand-500: var(--brand-500);
--color-brand-950: var(--brand-950);
/* …and the rest */
} Now bg-brand-50, text-brand-900, and border-brand-200 all resolve to your ramp. Tailwind v4 shipped its own default palette authored in OKLCH, so your custom scale sits right alongside the built-in ones with no impedance mismatch.
Which rung does which job
A scale is only useful if you know what each rung is for. Rough map:
- 50–100 — surfaces. Page tints, hover backgrounds, the faint fill behind a selected row.
- 200–300 — borders and dividers. Just enough color to separate without shouting.
- 400–500 — the brand itself. Primary buttons, active links, the thing you want people to click.
- 600–700 — interaction states. Hover and pressed versions of that button.
- 700–900 — text. Headings, body copy, anything that needs to read as dark-with-a-tint.
- 950 — near-black text sitting on a tinted panel, where plain black would look like a hole.
Once you assign roles, name them. That's the job of the Design Tokens tool — it lets you tag each rung by its purpose (--surface, --border, --text-strong) and export the set. If tokens are new to you, the design tokens explainer covers the why.
Build it without doing the math
You don't have to hand-tune eleven oklch() triples in a text editor. Open the Palette editor, drop in your one brand hex, and watch the full 50–950 ramp render live — nudge the lightness curve and the chroma taper until the middle pops and the ends stay calm. Preview the buttons and text against real components in the UI view before you commit. And if you need to flip any single value from hex to HSL to OKLCH, the Converter does it in one paste.
Common questions
How many steps do I really need?
Eleven is the safe default because it matches what Tailwind and Radix already use, so your team's muscle memory transfers. Can you ship with fewer? Sure — a small product survives on maybe six rungs: two surfaces, a border, the brand, one hover state, plus a text color. But building all eleven costs you nothing extra once the ramp is generated, and you'll reach for the in-between rungs sooner than you'd guess.
Do I have to use OKLCH, or is HSL good enough?
You can build a scale in HSL. It just won't be evenly spaced to the eye, so you'll spend an afternoon manually fudging the middle rungs to fight the muddy clump. OKLCH does that spacing for free. Author in OKLCH, and if a legacy tool needs hex, convert at the end.
Is my brand color 500 or 600?
Check its lightness. If your brand hex lands around L 0.62 in OKLCH, it's a natural 500. Darker and denser, closer to L 0.54? Call it 600 and let 500 be a slightly lighter tint above it. Neither is wrong. Anchor your base where its lightness actually falls, then build the rest of the scale around that anchor.
Grab your one hex, open the Palette editor, and let it spin up all eleven rungs in a few seconds.
Ready to try it? Every tool on Paleta runs free in your browser — no sign-up, nothing uploaded.
Explore the tools →