CSS Color Formats Compared: HEX vs RGB vs HSL vs OKLCH
CSS has accumulated a surprising number of ways to express color. From the ubiquitous #ff0000 to the perceptually uniform oklch(), each format exists for a reason. This guide compares them all so you can pick the right one for each situation.
HEX: the old reliable
HEX colors represent red, green, and blue channels as two-digit hexadecimal values:
color: #3b82f6; /* 6-digit */
color: #3b82f680; /* 8-digit with alpha */
color: #38f; /* 3-digit shorthand */
HEX is compact, universally supported, and the default output of every color picker. It is the format most developers reach for first.
Strengths: Compact notation, universal tooling support, easy to copy-paste from design tools.
Weaknesses: Not human-readable. Looking at #3b82f6, you cannot tell whether the color is warm or cool, light or dark, without memorizing the hex ranges. Adjusting lightness or saturation requires converting to another format first.
RGB and RGBA: explicit channels
RGB spells out the red, green, and blue values as decimals (0-255) or percentages:
color: rgb(59, 130, 246); /* classic syntax */
color: rgb(59 130 246); /* modern syntax (no commas) */
color: rgb(59 130 246 / 0.5); /* with alpha */
The modern syntax (space-separated, slash for alpha) is supported in all current browsers and is the recommended form going forward.
Strengths: Slightly more readable than HEX. Easy to manipulate programmatically since each channel is an independent number.
Weaknesses: Same fundamental problem as HEX -- the three channels do not map to how humans perceive color. Increasing the green channel does not predictably make a color "lighter" in the way you might expect.
HSL: designed for humans
HSL stands for Hue, Saturation, and Lightness. It maps to an intuitive mental model:
color: hsl(217 91% 60%); /* blue */
color: hsl(217 91% 60% / 0.5); /* with alpha */
- Hue (0-360): position on the color wheel. 0 is red, 120 is green, 240 is blue.
- Saturation (0-100%): intensity. 0% is gray, 100% is fully vivid.
- Lightness (0-100%): brightness. 0% is black, 100% is white, 50% is the "pure" color.
HSL is excellent for creating color palettes. Need a darker shade? Lower the lightness. Need a muted variant? Reduce saturation. Need a complementary color? Add 180 to the hue.
:root {
--brand-hue: 217;
--brand: hsl(var(--brand-hue) 91% 60%);
--brand-light: hsl(var(--brand-hue) 91% 80%);
--brand-dark: hsl(var(--brand-hue) 91% 35%);
}
Strengths: Intuitive manipulation. Building systematic palettes with CSS custom properties is straightforward.
Weaknesses: HSL's lightness channel is mathematically uniform but not perceptually uniform. An HSL lightness of 50% for yellow looks much brighter to the human eye than 50% for blue. This makes contrast calculations unreliable if you rely solely on the L value.
HWB: hue, whiteness, blackness
HWB is a lesser-known format that some designers find more intuitive than HSL:
color: hwb(217 10% 5%);
Think of it as starting with a pure hue and then mixing in white and black. It is supported in all modern browsers but rarely used in practice because HSL and OKLCH cover its use cases better.
OKLCH: the modern standard
OKLCH is a perceptually uniform color space. Colors that have the same lightness value in OKLCH actually look equally bright to human eyes, unlike HSL.
color: oklch(0.637 0.237 264); /* vivid blue */
color: oklch(0.637 0.237 264 / 0.5); /* with alpha */
- L (0-1): perceptual lightness. 0 is black, 1 is white.
- C (0-~0.4): chroma (color intensity). 0 is gray.
- H (0-360): hue angle, similar to HSL.
OKLCH solves the biggest problem with HSL: you can change the hue while keeping perceived brightness constant. This is critical for accessible color systems where you need consistent contrast ratios across an entire palette.
:root {
--success: oklch(0.72 0.19 145); /* green */
--warning: oklch(0.72 0.19 75); /* yellow */
--error: oklch(0.72 0.19 25); /* red */
/* All three have the same perceptual lightness */
}
Strengths: Perceptually uniform. Ideal for design systems and accessible palettes. Can represent colors outside the sRGB gamut for wide-gamut displays.
Weaknesses: Less intuitive chroma values (the max depends on the hue). Requires modern browsers (supported since 2023 in all major browsers).
Browser support summary
| Format | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| HEX | All | All | All | All |
| RGB | All | All | All | All |
| HSL | All | All | All | All |
| HWB | 101+ | 96+ | 15+ | 101+ |
| OKLCH | 111+ | 113+ | 15.4+ | 111+ |
For production code targeting modern browsers, all formats are safe. If you must support very old browsers, stick with HEX or RGB and use OKLCH as a progressive enhancement with fallbacks.
Accessibility and contrast
The Web Content Accessibility Guidelines (WCAG) require minimum contrast ratios between text and background: 4.5:1 for normal text and 3:1 for large text.
Calculating contrast in HEX or RGB requires converting to relative luminance first. HSL lightness is a poor proxy because it is not perceptually uniform. OKLCH lightness is much closer to perceived brightness, making it easier to estimate contrast by eye.
For precise checks, always use a dedicated contrast checker. But when building palettes, OKLCH gives you a reliable starting point: if two colors have a large difference in the L channel, they likely have good contrast.
Which format to use when
Use HEX when you need compact notation and are pasting values from Figma, Sketch, or a color picker. It is the universal interchange format.
Use RGB when you are manipulating colors programmatically in JavaScript and want straightforward channel arithmetic.
Use HSL when you are building a color palette with CSS custom properties and want intuitive control over shade, tint, and saturation.
Use OKLCH when you are building a design system that needs perceptually consistent colors, accessible contrast, or wide-gamut display support. It is the future of CSS color.
Converting between formats
Every format represents the same underlying color (within the sRGB gamut), so conversion is lossless. The math for RGB-to-HSL is straightforward. OKLCH conversion is more complex and typically handled by a library or tool.
// Quick RGB to HEX in JavaScript
function rgbToHex(r, g, b) {
return '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('');
}
// HEX to RGB
function hexToRgb(hex) {
const n = parseInt(hex.slice(1), 16);
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
For OKLCH conversions, use a library like culori or a browser-based tool to avoid implementing the complex matrix math yourself.
Try our Color Converter to convert between HEX, RGB, HSL, OKLCH, and more -- right in your browser, with a live preview of every format.