CSS Color Functions: rgb(), hsl(), oklch() Compared
CSS has more ways to write a color than most developers realize. Beyond the ubiquitous hex code, there are at least eight distinct color functions โ and each one has a different model, different strengths, and different reasons you might reach for it. Picking the right one is not just a syntax preference; it can determine whether your colors are accessible, whether they interpolate correctly in animations, and whether you can generate programmatic color scales that look visually consistent.
This guide covers every major CSS color function, when each one shines, and what the browser support situation looks like in 2026.
rgb() and rgba()
The rgb() function is the direct CSS expression of the RGB color model: three numbers from 0 to 255, one each for Red, Green, and Blue.
color: rgb(59, 130, 246); /* A vivid blue */
color: rgb(255, 87, 51); /* A warm coral red */
color: rgb(0, 0, 0); /* Black */
color: rgb(255, 255, 255); /* White */
rgba() is the historical form for adding opacity, with a fourth argument from 0 (transparent) to 1 (opaque):
color: rgba(59, 130, 246, 0.5); /* Same blue at 50% opacity */
Modern rgb() Syntax
In CSS Color Level 4 (fully supported in all modern browsers since 2023), rgb() was updated to accept the alpha channel directly, making rgba() redundant:
/* These are now equivalent */
color: rgba(59, 130, 246, 0.5);
color: rgb(59 130 246 / 0.5);
The new space-separated syntax with a slash before the alpha value works with rgb(), hsl(), and other functions. Both forms work โ you will see both in existing codebases.
When to Use rgb()
rgb() is most useful when:
- You are working from a hex color and want the raw channel values visible
- You need to animate individual R, G, or B channels via CSS custom properties
- You are generating colors programmatically and the source data is already in RGB
Its weakness is that human intuition does not map well to RGB. It is hard to look at rgb(107, 142, 35) and know you will get olive green. For design work, HSL or OKLCH are more intuitive.
hsl() and hsla()
HSL stands for Hue, Saturation, Lightness. It maps colors to a cylinder rather than a cube, which is much closer to how designers actually think about color.
- Hue: a degree on the color wheel from 0 to 360 (0 = red, 120 = green, 240 = blue)
- Saturation: 0% (gray, no color) to 100% (fully vivid)
- Lightness: 0% (black) to 100% (white), with 50% being the "pure" color
color: hsl(217, 91%, 60%); /* A vivid blue */
color: hsl(9, 100%, 60%); /* A warm coral red */
color: hsl(80, 60%, 35%); /* Olive green */
color: hsl(0, 0%, 50%); /* Pure medium gray */
hsla() adds the alpha channel, identical to the rgb() / rgba() pattern:
color: hsla(217, 91%, 60%, 0.5); /* Blue at 50% opacity */
color: hsl(217 91% 60% / 0.5); /* Same, using modern syntax */
Why hsl() Changed Web Development
Before hsl() arrived in CSS 2.1 (2011), changing a color's lightness or saturation in CSS required going back to a hex code or RGB values and recalculating from scratch. With hsl(), you can create consistent variations on a color trivially:
:root {
--blue-h: 217;
--blue-s: 91%;
}
.btn {
background: hsl(var(--blue-h), var(--blue-s), 60%); /* Base */
}
.btn:hover {
background: hsl(var(--blue-h), var(--blue-s), 50%); /* Darker on hover */
}
.btn:active {
background: hsl(var(--blue-h), var(--blue-s), 40%); /* Even darker */
}
The Problem with hsl()
Despite its intuitive appeal, HSL has a fundamental flaw: it is not perceptually uniform. Equal numerical steps in HSL do not produce colors that look equally different to the human eye.
The most obvious symptom is yellow. A pure HSL yellow (hsl(60, 100%, 50%)) appears much brighter and more intense than a pure HSL blue (hsl(240, 100%, 50%)) even though both have the same saturation and lightness values. This makes it difficult to build color scales where different hues feel visually balanced.
For this reason, modern CSS color work is increasingly moving toward OKLCH.
lab() and lch()
CSS Color Level 4 introduced lab() and lch() based on the CIE L*a*b* color space, designed by color scientists to be perceptually uniform โ equal numerical differences correspond to equal perceived differences in color.
lab()
lab() uses three axes:
- L: Lightness from 0 (black) to 100 (white)
- a: Position on the green-red axis (negative = green, positive = red)
- b: Position on the blue-yellow axis (negative = blue, positive = yellow)
color: lab(53 -5 -60); /* A vivid blue */
color: lab(55 60 40); /* A warm red-orange */
color: lab(80 -15 20); /* Soft olive/lime */
The a and b axes are less intuitive than hue degrees, which makes lab() somewhat difficult to write by hand. It is most useful programmatically โ for example, when computing perceptually uniform gradients or contrast ratios.
lch()
lch() is a cylindrical reimagining of Lab using Lightness, Chroma (similar to saturation), and Hue:
color: lch(53 60 265); /* Vivid blue (L=53, C=60, H=265ยฐ) */
color: lch(60 55 25); /* Warm orange-red */
The hue in lch() maps approximately (but not identically) to the hue in hsl(). The key difference is that lightness and chroma in lch() are perceptually meaningful โ two colors at lch(70 40 X) will look equally bright regardless of their hue, which is not true in hsl().
When to Use lab() and lch()
These functions are excellent for: - Generating accessible color pairs where perceived contrast is predictable - Building data visualization palettes where colors feel equally weighted - Creating smooth, natural-looking gradients
They have been superseded for most new work by oklch(), which is more accurate and has better tooling support.
oklch()
OKLCH is the current state of the art for CSS color specification. It is based on the Oklab color space, developed by Bjรถrn Ottosson in 2020, which improves on CIE Lab with better perceptual uniformity โ especially for blue and purple hues, which CIE Lab handles poorly.
The function takes three values plus an optional alpha:
- L: Lightness from 0 (black) to 1 (white)
- C: Chroma (color intensity), typically 0 to 0.4
- H: Hue angle in degrees, 0 to 360
color: oklch(0.62 0.19 250); /* Vivid blue */
color: oklch(0.65 0.22 25); /* Warm red-orange */
color: oklch(0.85 0.08 130); /* Soft green */
color: oklch(0.62 0.19 250 / 0.5); /* Blue at 50% opacity */
Why oklch() Matters
Predictable lightness. Because OKLCH lightness is perceptually uniform, two colors with the same L value look equally bright, regardless of hue. This makes it straightforward to build color palettes where every hue feels balanced:
/* All of these look equally "medium weight" in OKLCH */
--red: oklch(0.65 0.25 15);
--green: oklch(0.65 0.18 145);
--blue: oklch(0.65 0.19 250);
--yellow: oklch(0.65 0.15 90);
Try achieving that with hsl() โ the yellow will look noticeably brighter than the blue.
Wide gamut access. OKLCH can express colors outside the sRGB gamut, giving access to the vivid colors available on modern displays (iPhone, MacBook Pro, most 2023+ laptops) that support Display P3 or wider. Hex codes and rgb() are limited to sRGB:
/* This vivid green is only possible with oklch โ outside sRGB */
color: oklch(0.72 0.30 145);
Browsers automatically map out-of-gamut OKLCH colors to the nearest available color for displays that cannot show them, so it is safe to use wide gamut colors now.
Better gradient interpolation. Colors interpolated (animated or gradated) in OKLCH pass through perceptually intermediate colors. The infamous "gray muddy middle" that appears in RGB gradients between complementary colors does not occur in OKLCH.
oklch() in Design Systems
An increasing number of design systems and CSS frameworks are adopting OKLCH for their color scales. If you are building a design system from scratch in 2025 or later, OKLCH is the recommended starting point. Use the Color Converter to translate any existing hex code into its OKLCH equivalent.
color-mix()
color-mix() is a newer CSS function that blends two colors in a specified color space and proportion:
/* Mix blue and red equally in oklch */
color: color-mix(in oklch, #3B82F6 50%, #EF4444 50%);
/* Create a lighter tint: mix with white */
color: color-mix(in oklch, oklch(0.62 0.19 250) 60%, white);
/* Create a darker shade: mix with black */
color: color-mix(in oklch, oklch(0.62 0.19 250) 70%, black);
The in oklch (or in srgb, in hsl, etc.) specifies the color space used for the blending calculation, which significantly affects the result.
Why the Color Space Matters for Mixing
Blending in sRGB often produces dull, desaturated midpoints between complementary colors. Blending in OKLCH produces perceptually vibrant intermediate colors โ because OKLCH interpolation follows the same path human color perception takes.
/* Blending blue and yellow in sRGB often produces a dull olive/gray */
color: color-mix(in srgb, blue, yellow);
/* Blending in oklch produces a vivid green intermediate */
color: color-mix(in oklch, blue, yellow);
Practical Use Cases for color-mix()
Generating theme variants. Instead of hardcoding five shade variants of your brand color, generate them dynamically:
:root {
--brand: oklch(0.55 0.22 250);
--brand-light: color-mix(in oklch, var(--brand) 50%, white);
--brand-lighter: color-mix(in oklch, var(--brand) 25%, white);
--brand-dark: color-mix(in oklch, var(--brand) 80%, black);
}
Dark mode adaptation. Automatically derive dark-mode surface colors by mixing with a dark base:
@media (prefers-color-scheme: dark) {
--surface: color-mix(in oklch, var(--brand) 15%, #121212);
}
Opacity without actual transparency. Mixing a color with white or a specific background color simulates opacity without compositing layers:
/* Simulates rgba(59, 130, 246, 0.2) on white background */
color: color-mix(in srgb, #3B82F6 20%, white);
Browser Support in 2026
| Feature | Chrome | Firefox | Safari | Edge | Global support |
|---|---|---|---|---|---|
rgb() / rgba() |
All | All | All | All | ~100% |
hsl() / hsla() |
All | All | All | All | ~100% |
| Space-separated syntax | 111+ | 113+ | 15.4+ | 111+ | ~95% |
lab() / lch() |
111+ | 113+ | 15+ | 111+ | ~92% |
oklch() |
111+ | 113+ | 15.4+ | 111+ | ~92% |
color-mix() |
111+ | 113+ | 16.2+ | 111+ | ~90% |
Wide gamut oklch() |
111+ | 113+ | 15.4+ | 111+ | ~92% |
As of early 2026, oklch() and color-mix() are supported across all major current browsers. The primary concern is users on older mobile browsers or enterprise environments with outdated browser policies. For production work where maximum compatibility matters, provide a hex or rgb() fallback before the modern syntax:
.element {
/* Fallback for older browsers */
color: #3B82F6;
/* Progressive enhancement */
color: oklch(0.62 0.19 250);
}
Choosing the Right CSS Color Function
| Use case | Recommended function |
|---|---|
| Quick, familiar color specification | hex or rgb() |
| HSL-intuitive adjustments (lighter/darker) | hsl() |
| Perceptually balanced palettes | oklch() |
| Wide gamut / vivid screen colors | oklch() |
| Gradient and animation interpolation | oklch() |
| Programmatic color mixing | color-mix(in oklch, ...) |
| Accessibility-focused contrast work | oklch() or lab() |
Key Takeaways
rgb()andhsl()are the established standards with universal browser support. The modern space-separated syntax (rgb(R G B / alpha)) makesrgba()andhsla()redundant.hsl()is more intuitive thanrgb()for design work, but it is not perceptually uniform โ equal numerical steps produce unequal perceptual changes, especially across hues.lab()andlch()introduced perceptual uniformity to CSS, but have been largely superseded by the more accurateoklch().oklch()is the modern standard: perceptually uniform, wide-gamut capable, and excellent for gradients, animations, and design system color scales.color-mix()enables dynamic color generation directly in CSS โ useful for themes, tints, shades, and dark mode adaptation.- Use the Color Converter to translate any existing hex code into
oklch()or any other format. - Provide fallback hex or
rgb()values beforeoklch()orcolor-mix()for maximum browser compatibility.