Colors
The v4 oklch palette, the opacity modifier, every colour-accepting utility, and how to define an accessible palette with @theme.
Tailwind v4 ships a default palette of 25 colour families in the OKLCH colour space, each with 11 steps from 50 to 950.
The palette#
| Group | Families |
|---|---|
| Warm | red orange amber yellow |
| Green | lime green emerald teal |
| Blue | cyan sky blue indigo |
| Purple/pink | violet purple fuchsia pink rose |
| Neutral | slate gray zinc neutral stone |
| Muted v4.2 | mauve olive mist taupe |
Plus black, white, transparent, current and inherit.
<div class="space-y-2 p-4">
<div class="flex overflow-hidden rounded-lg">
<div class="h-10 flex-1 bg-indigo-50"></div><div class="h-10 flex-1 bg-indigo-100"></div><div class="h-10 flex-1 bg-indigo-200"></div><div class="h-10 flex-1 bg-indigo-300"></div><div class="h-10 flex-1 bg-indigo-400"></div><div class="h-10 flex-1 bg-indigo-500"></div><div class="h-10 flex-1 bg-indigo-600"></div><div class="h-10 flex-1 bg-indigo-700"></div><div class="h-10 flex-1 bg-indigo-800"></div><div class="h-10 flex-1 bg-indigo-900"></div><div class="h-10 flex-1 bg-indigo-950"></div>
</div>
<div class="flex justify-between px-1 font-mono text-[0.65rem] text-gray-500">
<span>50</span><span>300</span><span>500</span><span>700</span><span>950</span>
</div>
</div>Choosing a neutral#
The five greys differ in temperature. Pick one and stay with it.
| Family | Undertone | Pairs well with |
|---|---|---|
slate | Cool blue | Blues, indigos |
gray | Slightly cool | Almost anything |
zinc | Neutral | Minimal, technical UI |
neutral | Pure grey | Photography, art direction |
stone | Warm | Oranges, ambers, editorial |
OKLCH#
The palette is defined in oklch():
--color-red-500: oklch(63.7% 0.237 25.331);
--color-blue-600: oklch(54.6% 0.245 262.881);OKLCH is perceptually uniform, so 500 looks equally bright across every family — something the old hex palette could not guarantee. It also reaches colours outside sRGB on wide-gamut displays.
Where colours apply#
| Utility | Property |
|---|---|
text-* | color |
bg-* | background-color |
border-* | border-color |
border-t-*, border-x-* | Per-side border colour |
divide-* | Borders between children |
outline-* | outline-color |
ring-* | Box-shadow ring |
inset-ring-* | Inset ring |
shadow-* | Coloured box shadow |
inset-shadow-* | Inset shadow colour |
drop-shadow-* | Coloured drop shadow v4.1 |
text-shadow-* | Text shadow colour v4.1 |
decoration-* | text-decoration-color |
accent-* | Native form control accent |
caret-* | Text cursor |
fill-* / stroke-* | SVG |
from-* / via-* / to-* | Gradient stops |
placeholder:text-* | Placeholder text |
selection:bg-* | Highlighted text |
scrollbar-thumb-* / scrollbar-track-* | Scrollbars v4.3 |
Opacity modifier#
Append / and a percentage to any colour utility.
<div class="bg-sky-500/50">50%</div>
<div class="bg-pink-500/[71.37%]">Arbitrary</div>
<div class="bg-cyan-400/(--my-alpha)">From a variable</div>It compiles to color-mix(in oklab, …), so it works with any colour — including CSS variables and currentColor.
/ vs opacity-*#
bg-black/50 | opacity-50 | |
|---|---|---|
| Affects | Just that colour | The whole element, including children |
| Creates a stacking context | No | Yes |
| Right for | Translucent surfaces, overlays | Fading an entire element |
The stacking-context difference matters — see Overflow & Layering.
Special values#
| Value | Meaning |
|---|---|
transparent | Fully transparent |
current | Inherits currentColor |
inherit | Inherits the parent's value |
black / white | Pure |
text-current is how you make an SVG follow its parent's text colour:
<button class="text-indigo-600 hover:text-indigo-500">
<svg class="size-4 fill-current">…</svg>
Continue
</button>Customising#
Add colours#
@theme {
--color-brand-50: oklch(0.97 0.02 264);
--color-brand-500: oklch(0.62 0.19 264);
--color-brand-900: oklch(0.35 0.14 264);
}Any colour syntax works — #4f46e5, rgb(79 70 229), hsl(243 75% 59%) — but OKLCH gives the most even scales.
Override a default#
@theme {
--color-gray-500: oklch(0.55 0.02 260);
}Remove families you do not use#
@theme {
--color-lime-*: initial;
--color-fuchsia-*: initial;
}Replace the palette entirely#
@theme {
--color-*: initial;
--color-white: #fff;
--color-black: #000;
--color-ink: oklch(0.25 0.02 264);
--color-brand: oklch(0.62 0.19 264);
}Now only your colours exist — a strong guardrail on a design system.
Semantic aliases#
@theme {
--color-success: var(--color-emerald-600);
--color-danger: var(--color-red-600);
--color-warning: var(--color-amber-500);
}<p class="text-danger">Something went wrong.</p>Accessibility#
WCAG 2.1 AA requires:
- 4.5:1 for body text
- 3:1 for large text (18.66px bold, or 24px)
- 3:1 for UI components and meaningful graphics
Approximate ratios against white:
| Colour | Ratio | Body text? |
|---|---|---|
gray-400 | ~2.8:1 | ✗ |
gray-500 | ~4.6:1 | ✓ |
gray-600 | ~7.0:1 | ✓ |
indigo-500 | ~4.6:1 | ✓ (borderline) |
indigo-600 | ~6.3:1 | ✓ |
yellow-400 | ~1.4:1 | ✗ — never for text on white |
Common patterns#
Tinted status badges#
<div class="flex flex-wrap gap-2 p-4 text-xs font-medium">
<span class="rounded-full bg-emerald-50 px-2.5 py-1 text-emerald-700 ring-1 ring-emerald-600/20 ring-inset">Active</span>
<span class="rounded-full bg-amber-50 px-2.5 py-1 text-amber-800 ring-1 ring-amber-600/20 ring-inset">Pending</span>
<span class="rounded-full bg-red-50 px-2.5 py-1 text-red-700 ring-1 ring-red-600/20 ring-inset">Failed</span>
<span class="rounded-full bg-gray-50 px-2.5 py-1 text-gray-600 ring-1 ring-gray-500/20 ring-inset">Archived</span>
</div>The pattern — 50 background, 700 text, 600/20 ring — works for every family.
Translucent overlays#
<div class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm"></div>Colour with --alpha() in custom CSS#
.highlight {
background-color: --alpha(var(--color-yellow-300) / 35%);
}Common mistakes#
- Using
*-opacity-*utilities. Removed in v4. - Building class names from variables. `
bg-${color}-500` produces nothing — map to full class names. opacity-50wherebg-black/50was meant. It fades the text too, and creates a stacking context.- Mixing neutral families.
slateborders withzinctext looks subtly off. - Light grey text.
text-gray-400on white fails contrast. Use500or darker.
Related topics#
- Backgrounds & Gradients — gradient stops
- Dark Mode — light/dark colour pairs
- Theme & Design Tokens — the
--color-*namespace - Shadows — coloured shadows and rings