Building an OKLCH color palette tailwind v4 actually wants means two changes from the old way: your shades live in the OKLCH color space (not hex), and your brand colors get defined in CSS inside an @theme block instead of a JavaScript config. Do both and you get a 50-900 scale where every step looks evenly spaced, your darks stay rich instead of muddy, and your lightest tints do not blow out to washed neon. This guide walks the whole pipeline, from picking a brand hue to pasting variables into @theme.
Most palette generators still hand you hex codes and stop there. That worked for Tailwind v3. In v4 the default color system was rebuilt on OKLCH, so pasting hex back in throws away the exact thing that makes the new palette feel balanced. Below you will see why OKLCH avoids the classic "muddy 700, neon 100" problem, how lightness, chroma, and hue map onto a scale, and the exact @theme syntax to ship it.
Why Tailwind v4 Switched to OKLCH#
Tailwind v4 ships its entire default palette in OKLCH, and it expects your custom colors to follow. OKLCH stands for Lightness, Chroma, Hue, with an optional alpha. It is a perceptually uniform color space, meaning a change in the lightness number looks like the same amount of visual change to your eye no matter which hue you are on.
That sounds academic until you compare it to the spaces we used before. Hex and HSL are not perceptually uniform. In HSL, "50% lightness" on yellow looks far brighter than "50% lightness" on blue, so a palette built by evenly stepping HSL lightness comes out lopsided. Some shades look too dark, others too pale, and nothing lines up across colors.
Key idea: OKLCH decouples how bright a color looks from its hue. Step the lightness evenly and the shades feel evenly spaced, whether the color is lemon or navy.
What OKLCH fixes in practice#
Two failure modes disappear once you author in OKLCH:
- Muddy darks: In HSL, darkening a saturated color often drags it toward gray or brown. OKLCH lets you drop lightness while holding chroma, so a 700 or 800 shade stays a rich, recognizable version of the brand color instead of sludge.
- Neon lights: HSL tints near white can spike in apparent saturation and look radioactive. OKLCH tints stay soft and controlled because chroma is managed independently of lightness.
The wider gamut bonus#
OKLCH can also describe colors outside the old sRGB range. On modern displays that support the P3 gamut (most recent phones and laptops), Tailwind v4 colors render more vivid than the sRGB equivalents, then gracefully fall back on older screens. You get richer color for free on capable hardware without breaking anything on the rest. This is why P3 gamut colors matter for brand work in 2026: the same CSS looks punchier where it can and safe everywhere else.
How Lightness, Chroma, and Hue Map to a 50-900 Scale#
A Tailwind color scale runs from 50 (lightest tint) to 950 (darkest shade), with the brand color usually sitting at 500. The trick to a clean OKLCH scale is understanding which of the three channels you move at each step, and which you mostly leave alone.
Here is the mental model:
| Channel | What it controls | How it moves across 50 to 950 |
|---|---|---|
| Lightness (L) | How bright the color looks, 0 to 1 | Highest at 50 (around 0.97), lowest at 950 (around 0.25). This does most of the work. |
| Chroma (C) | Color intensity / saturation | Lowest at the extremes (tints and darks), peaks in the mid-range (400-600). |
| Hue (H) | The actual color angle, 0 to 360 | Stays nearly constant. Small shifts add realism but are optional. |
The single most important rule: lightness should step in roughly even increments. If your 500 sits near L 0.65, your scale might run something like 0.97, 0.94, 0.89, 0.82, 0.74, 0.65, 0.55, 0.45, 0.37, 0.30 from 50 to 900. Even spacing in L is what makes the swatches feel like a true gradient rather than random samples.
Chroma is a curve, not a line#
The mistake people make is holding chroma flat across the scale. That produces washed-out tints and oversaturated darks. Real, pleasant palettes use a chroma arch:
- Tints (50-200): low chroma. A near-white tint with high chroma looks artificial, so pull it down.
- Core (400-600): peak chroma. This is where the brand color is most vivid.
- Shades (700-950): chroma tapers down again, partly because deep colors cannot hold high chroma without clipping outside the gamut.
Picture chroma rising from the lightest tint to a peak around 400-500, then easing back down toward the darkest shade. That arch is the difference between a scale that looks designed and one that looks generated.
Hue: leave it alone, mostly#
For a clean, modern scale, keep hue constant across all eleven steps. If you want extra polish, you can nudge hue by a few degrees, a technique called hue shifting, to mimic how real pigments behave (shadows drift cooler, highlights warmer). It is optional and easy to overdo, so start with a constant hue and add subtle shifts only if the flat version feels lifeless.
How to Build an OKLCH Palette for Tailwind v4#
You can hand-calculate this, but it is fiddly and easy to get the chroma curve wrong. The fastest reliable path is to generate the scale from a single brand color, then paste the OKLCH values into your stylesheet. Here is the full procedure.
Step 1: Pick your brand color as the 500 anchor#
Start with one color, the one that represents your brand, and treat it as the 500 step. Do not start from the lightest or darkest end; the mid-tone is the anchor everything else derives from. If you only have a hex code from a logo, that is fine, you will convert it. Open the free color palette generator, drop in your brand hex or pick a hue, and let it become your 500.
Step 2: Generate the full 50 to 900 scale in OKLCH#
From the 500 anchor, generate the surrounding steps. A good generator does the lightness stepping and the chroma arch for you, so you get tints that stay soft and shades that stay rich. Confirm the output is in OKLCH, not hex. You want values that look like oklch(0.65 0.18 250), three numbers for lightness, chroma, and hue. If a tool only gives you hex, it is doing Tailwind v3 output, and you will lose the benefit.
Step 3: Sanity-check the scale by eye#
Look at the swatches in order. Two quick tests:
- Do the steps feel evenly spaced, with no sudden jump where one shade looks much closer to its neighbor than the rest?
- Are the darks still clearly your brand color, and are the tints soft rather than neon?
If a step looks off, it is almost always a lightness spacing issue or a chroma value that did not taper. Adjust that one step rather than rebuilding the whole scale.
Step 4: Convert each step to a CSS variable#
Tailwind v4 reads colors from CSS custom properties. Name them with the convention --color-brand-50 through --color-brand-950. The --color- prefix is what tells Tailwind to generate matching utilities. Each variable holds one OKLCH value:
:root {
--color-brand-50: oklch(0.97 0.02 250);
--color-brand-100: oklch(0.94 0.04 250);
--color-brand-200: oklch(0.89 0.07 250);
--color-brand-300: oklch(0.82 0.11 250);
--color-brand-400: oklch(0.74 0.15 250);
--color-brand-500: oklch(0.65 0.18 250);
--color-brand-600: oklch(0.55 0.17 250);
--color-brand-700: oklch(0.45 0.14 250);
--color-brand-800: oklch(0.37 0.10 250);
--color-brand-900: oklch(0.30 0.07 250);
--color-brand-950: oklch(0.22 0.04 250);
}
Notice the chroma column: it rises to 0.18 at 500, then tapers in both directions. That is the arch in action.
Step 5: Wire the scale into the @theme block#
This is the part that is genuinely new in v4. Instead of editing tailwind.config.js, you declare theme colors in CSS inside an @theme block, right in your main stylesheet:
@import "tailwindcss";
@theme {
--color-brand-50: oklch(0.97 0.02 250);
--color-brand-100: oklch(0.94 0.04 250);
--color-brand-200: oklch(0.89 0.07 250);
--color-brand-300: oklch(0.82 0.11 250);
--color-brand-400: oklch(0.74 0.15 250);
--color-brand-500: oklch(0.65 0.18 250);
--color-brand-600: oklch(0.55 0.17 250);
--color-brand-700: oklch(0.45 0.14 250);
--color-brand-800: oklch(0.37 0.10 250);
--color-brand-900: oklch(0.30 0.07 250);
--color-brand-950: oklch(0.22 0.04 250);
}
Because the names start with --color-, Tailwind automatically generates the utilities: bg-brand-500, text-brand-700, border-brand-200, and so on, plus the matching brand value in arbitrary syntax. No JS config, no plugin, no restart needed beyond the normal dev rebuild.
Warning: variables that define your design tokens must live inside
@theme, not a plain:rootblock, if you want Tailwind to generate utilities from them. A:rootvariable is just a regular CSS variable Tailwind ignores. Use@themefor tokens,:rootonly for one-off runtime values.
Step 6: Use the utilities and verify on a real screen#
Apply the classes and check the result on an actual device, ideally one with a P3 display and one without. The colors should look vivid on the wide-gamut screen and correct (not broken) on the sRGB one. If you support dark mode, you can redefine the same variables inside a @theme scoped to your dark selector, swapping which end of the scale maps to your surfaces.
OKLCH vs Hex: What Actually Changes in Your Workflow#
For developers coming from v3, the shift can feel like extra ceremony. It is not, once you see the payoff. Here is the honest before-and-after.
| Aspect | Tailwind v3 (hex/HSL) | Tailwind v4 (OKLCH) |
|---|---|---|
| Where colors live | tailwind.config.js (JavaScript) | @theme block in CSS |
| Color format | Hex or HSL | OKLCH (L, C, H) |
| Scale evenness | Manual tuning, often uneven | Even lightness steps look balanced |
| Dark shades | Tend to go muddy | Stay rich at low lightness |
| Wide gamut | sRGB only | P3 gamut on capable displays |
| Editing one shade | Recompute hex by hand | Nudge one number (L, C, or H) |
The day-to-day win is that last row. Want a slightly punchier 600? Bump its chroma by 0.02. Want the whole scale a touch lighter? Add 0.02 to every lightness value. Because the channels are independent and perceptual, small numeric edits produce predictable visual changes, instead of the guess-and-check you do with hex.
When you still need hex#
Some destinations do not speak OKLCH yet: certain email clients, older design tools, some native platforms. For those, export a hex fallback alongside your OKLCH theme. Tailwind v4 itself handles browser fallback automatically for modern browsers, but if you are handing colors to a non-CSS surface, keep a converted hex sheet. A good generator gives you both representations from the same source color so they never drift apart.
Putting It Together with Other Theme Tokens#
A palette rarely ships alone. Once your brand scale is in @theme, the same pattern extends to the rest of your theme color variables: neutrals, semantic colors (success, warning, danger), and surfaces. Build each as its own OKLCH scale with the same lightness rhythm, and your entire system stays visually coherent because every color shares the same perceptual spacing.
If your design also leans on gradients or layered effects, define those from the same OKLCH tokens so the gradient endpoints match your palette exactly. A quick way to prototype gradient stops that align with your brand scale is the CSS gradient generator, which lets you preview blends before committing them to a token. And when you need to ship the resulting brand mark or swatch sheet as an asset, the SVG to PNG converter turns a vector export into a raster file for slides or social.
For the conceptual side of choosing the right base hues before you ever open a generator, the walkthrough on a color palette generator for brand design covers harmony rules and accessibility contrast, which pair naturally with the OKLCH mechanics here.
Conclusion#
An OKLCH color palette tailwind v4 expects is not just a format change; it is a better way to think about color. Anchor on your brand color at 500, step lightness evenly from 50 to 950, arch the chroma up to the middle and back down, hold hue mostly constant, then declare the whole scale as --color-brand-* variables inside an @theme block. Do that and you get balanced, vivid, non-muddy shades plus free P3 richness on modern screens. Generate the scale, paste it into @theme, and ship.
Frequently Asked Questions#
What is OKLCH and why does Tailwind v4 use it? OKLCH is a perceptually uniform color space defined by Lightness, Chroma, and Hue. Tailwind v4 adopted it because evenly stepping the lightness produces shades that look evenly spaced to the human eye, which hex and HSL cannot do. The result is balanced 50-950 scales with rich darks and soft tints instead of muddy or neon extremes.
Where do I define custom colors in Tailwind v4?
Inside an @theme block in your CSS, not in a JavaScript config file. Declare each shade as a custom property like --color-brand-500: oklch(0.65 0.18 250);. Because the names start with --color-, Tailwind automatically generates utilities such as bg-brand-500 and text-brand-700. Plain :root variables do not generate utilities, so use @theme for design tokens.
How do lightness, chroma, and hue map to a 50-900 scale? Lightness does most of the work: highest at 50, lowest at 950, stepping in even increments. Chroma follows an arch, low at the tint and shade extremes and peaking around 400-600 where the color is most vivid. Hue stays nearly constant across the whole scale, with only optional small shifts for added realism.
Why do my hex-based dark shades look muddy but OKLCH ones do not? In HSL and hex workflows, darkening a saturated color often drags it toward gray or brown because lightness and saturation are entangled. OKLCH separates them, so you can lower lightness for a 700 or 800 shade while holding chroma, keeping the color a rich, recognizable version of your brand instead of sludge.
Do I still need hex color values with Tailwind v4? For the web, no: modern browsers read OKLCH and Tailwind handles fallbacks. You may still want hex for destinations that do not support OKLCH, like some email clients, older design tools, or native platforms. Generate both from the same source color so the representations never drift apart.
Can I generate an OKLCH Tailwind scale automatically?
Yes. A palette generator that outputs OKLCH will handle the even lightness stepping and the chroma arch for you from a single brand color, then give you the --color-brand-* lines to paste into @theme. That is faster and more reliable than hand-calculating each step, which is easy to get wrong on the chroma curve.



