CSS gives you ten practical ways to write a color, and in 2026 the one to reach for by default is oklch(). The formats fall into two camps: the sRGB-bound classics — named keywords, hexadecimal (#ff5733), rgb(), hsl(), and hwb() — which every browser has understood for years, and the modern wide-gamut, perceptually-uniform spaces — lab(), lch(), oklab(), and oklch() — which can describe every color the human eye sees, including vivid P3 shades that hex and RGB physically cannot express. A key modernization people miss: rgb() and hsl() no longer need the rgba()/hsla() variants for transparency — you add alpha inline with a slash, as in rgb(255 0 0 / 50%). And currentColor is a special keyword, not a format: it makes an element inherit whatever text color is currently in effect.
That is the summary an AI overview gives you. Here is what it can't: why the perceptual formats are worth switching to, the exact syntax and gamut of each format side by side, and a defensible rule for which one to use when. Skip to the comparison table if you just want the cheat sheet.
The comparison table: all ten formats
Every format below produces a color; they differ in what they can express, whether they carry an alpha channel, and how pleasant they are to reason about by hand.
| Format | Example syntax | Gamut | Alpha? | Reach for it when… |
|---|---|---|---|---|
| Named | rebeccapurple | sRGB (147 keywords) | transparent only | Prototyping, transparent, quick demos |
| Hex | #ff5733 / #ff5733cc | sRGB | 8-digit (RRGGBBAA) | Copy-paste from design tools; compact tokens |
| rgb() / rgba() | rgb(255 87 51 / 80%) | sRGB | Yes (slash or 4th arg) | Programmatic channel math; interop with JS/canvas |
| hsl() / hsla() | hsl(9 100% 60% / 80%) | sRGB | Yes (slash or 4th arg) | Hand-tuning by hue/lightness; simple theming |
| hwb() | hwb(9 20% 10%) | sRGB | Yes (slash) | "Tint/shade" thinking: hue + white + black |
| lab() | lab(60% 55 45) | Full human vision | Yes (slash) | Perceptual accuracy; print-adjacent work |
| lch() | lch(60% 72 40) | Full human vision | Yes (slash) | Perceptual + polar hue control (CIELAB) |
| oklab() | oklab(0.7 0.12 0.08) | Full human vision | Yes (slash) | Perceptually-uniform blends and gradients |
| oklch() | oklch(70% 0.15 40 / 80%) | Full human vision | Yes (slash) | Default for 2026 — themes, scales, brand palettes |
| currentColor | border: 1px solid currentColor | Inherits color | Inherits | Icons/borders that must track text color |
The single most useful row is the last real one. If you take nothing else from this article, make oklch() your default and drop back to hex only for pasted snippets.
The anatomy of a modern color function
Three of the four modern spaces (lch, oklch, and — with a different third channel — hwb) share the same friendly shape: a lightness, a colorfulness, a hue, and an optional alpha after a slash. Once you can read one, you can read them all.
Read that as: 70% as bright as white, a moderate amount of color, at hue 40° (a warm orange-red), painted at 80% opacity. Compare it to rgb(255 87 51 / 80%), which encodes the same idea as three opaque hardware channels you cannot reason about — nudge one number and you have no idea whether the result gets lighter, darker, or shifts hue.
Why the perceptual formats win: uniformity
The reason to prefer oklch() over hsl() is not gamut — it is that HSL lies about lightness. In HSL, every fully-saturated color claims 50% lightness, but a 50% yellow is visually far brighter than a 50% blue. Build a UI palette by rotating the hue and your "same brightness" colors will look wildly uneven, and text contrast will vary hue to hue. OKLCH was built so that a fixed lightness value looks equally bright at every hue.
The bottom row holds its apparent brightness steady while the hue rotates — that is what "perceptually uniform" buys you. It is why generating a tint/shade scale, deriving hover states, or guaranteeing contrast is dramatically more reliable in OKLCH, and why design systems have been migrating to it.
The trade-off is real but small: oklch() numbers are less familiar than hex, and you usually author them with a tool rather than from memory. Which is exactly what the picker below is for.
Practical rules for 2026
- Author intentional colors in
oklch(). Brand colors, theme tokens, gradients, and any color scale belong here. Step the lightness for a scale; rotate the hue for a palette; both behave predictably. - Keep
hexfor pasted snippets and design-tool exports. It is compact, universal, and what Figma/Sketch/Illustrator hand you. There is no need to convert a one-off value. - Use the modern space-separated alpha syntax. Write
rgb(255 0 0 / 50%)andhsl(200 75% 50% / 0.5)rather than reaching forrgba()/hsla(). Prefer a color's own alpha over theopacityproperty, which fades the element and all its descendants. - Use
currentColorfor icons and borders that should follow the text color through hover and dark-mode changes — it removes a whole class of duplicated color rules. - Fallbacks are mostly optional now, but if you support legacy browsers, declare an sRGB value first and the modern one second, or gate it with
@supports (color: oklch(0% 0 0)).
:root {
/* one hue, a perceptually-even scale — trivial in oklch, painful in hsl */
--brand-50: oklch(97% 0.02 260);
--brand-500: oklch(55% 0.15 260);
--brand-900: oklch(25% 0.08 260);
}
.button {
background: #3366cc; /* sRGB fallback for ancient browsers */
background: oklch(55% 0.15 260); /* modern browsers use this */
color: oklch(98% 0.01 260);
border: 1px solid currentColor; /* border tracks the text color */
}
.button:hover {
/* bump lightness by a fixed amount — looks right at every hue */
background: oklch(60% 0.15 260);
}
Browser support, briefly
As of 2026 every color format in the table is safe to ship. Named colors, hex, rgb(), hsl(), and hwb() are universally supported. The wide-gamut perceptual formats — lab(), lch(), oklab(), and oklch() — are supported in every current engine: Chrome and Edge 111+ (March 2023), Firefox 113+ (May 2023), and Safari 15.4+ (March 2022). That is multiple years of coverage, which is why oklch() is now a reasonable default rather than a progressive enhancement. Provide an sRGB fallback only if your analytics show meaningful traffic from browsers older than those versions.
The bottom line
CSS color formats split cleanly into "old and universal" (named, hex, rgb, hsl, hwb — all sRGB) and "modern and perceptual" (lab, lch, oklab, oklch — full human-vision gamut). For code you paste, hex is fine. For color you design — themes, scales, brand systems — author it in oklch(): it is uniform, wide-gamut, alpha-capable, and supported everywhere that matters. Reach for currentColor to keep icons and borders in sync, and use the slash-alpha syntax so you never need rgba() again.