Why Developers Are Switching From RGB to HSL for UI Colors

Side by side comparison of RGB and HSL color models with code editor styling and color swatches

The HSL color model (hue, saturation, lightness) gives developers a far more human-readable way to work with color in CSS compared to RGB. Instead of guessing which combination of red, green, and blue values produces the shade you want, HSL lets you describe color the way your brain actually thinks about it: pick a color, decide how vivid it is, then decide how light or dark it should be. That shift in mental model is exactly why so many developers are ditching rgb() in favor of hsl() .

RGB vs HSL - The Core Difference

RGB defines color by mixing three light channels: red, green, and blue, each on a scale from 0 to 255. The problem is that this is how screens render pixels, not how humans perceive color. If you want a slightly lighter blue in RGB, you have to increase all three channels in just the right proportion - there is no single knob to turn.

HSL maps directly to how people describe color in conversation:

  • Hue - the actual color, expressed as a degree on a 360-degree color wheel. Red is 0, green is 120, blue is 240.
  • Saturation - how vivid or washed-out the color is, from 0% (pure gray) to 100% (fully vivid).
  • Lightness - how light or dark the color is, from 0% (black) to 100% (white), with 50% being the "pure" color.

Here is the same medium blue expressed in both formats:

/* RGB - what do these numbers even mean at a glance? */
color: rgb(70, 130, 180);

/* HSL - instantly readable: blue hue, moderate saturation, medium lightness */
color: hsl(207, 44%, 49%);

Both produce steel blue. But only the HSL version tells you something useful at a glance. The hue is 207 (blue-ish), saturation is moderate at 44%, and lightness sits in the middle at 49%. You can reason about it without a color picker open.

Property RGB HSL
Readable by humans Rarely Yes
Lighten/darken a color Adjust 3 values Adjust lightness only
Change saturation Recalculate all 3 Adjust saturation only
Create color palette Trial and error Rotate hue by degrees
Theming with CSS variables Verbose Clean and predictable

HSL Syntax in CSS

The CSS hsl() function has been supported since CSS3. The modern syntax (CSS Color Level 4) also allows a fourth alpha parameter directly inside hsl() , making hsla() largely redundant now:

/* Classic syntax */
color: hsl(207, 44%, 49%);

/* With alpha (transparency) - old way */
color: hsla(207, 44%, 49%, 0.8);

/* Modern CSS Color Level 4 syntax - commas optional, alpha with slash */
color: hsl(207 44% 49%);
color: hsl(207 44% 49% / 0.8);
color: hsl(207 44% 49% / 80%);

All major browsers support both syntaxes. The comma-free version is the direction CSS is heading, but the comma version works everywhere including Internet Explorer 9+.

Why HSL Wins for UI Development

The real power of HSL shows up the moment you need to generate variations of a color - which happens constantly in UI work.

Building a color palette in seconds

Say your brand color is a vivid green at hsl(140, 70%, 45%) . You need a hover state, a disabled state, and a light background tint. In HSL, you only touch one value each time:

--color-base:     hsl(140, 70%, 45%);  /* base green */
--color-hover:    hsl(140, 70%, 38%);  /* darker - just lower lightness */
--color-disabled: hsl(140, 20%, 65%);  /* washed out - lower saturation */
--color-tint:     hsl(140, 70%, 92%);  /* very light background tint */

Try doing that predictably in RGB without a color picker open. It is genuinely difficult because there is no isolated axis to adjust.

Analogous and complementary colors

Since hue is a degree on a color wheel, generating harmonious palettes is arithmetic. Analogous colors are within 30 degrees of each other. Complementary colors are 180 degrees apart:

--primary:       hsl(210, 80%, 50%);   /* blue */
--analogous-1:   hsl(180, 80%, 50%);   /* cyan - 30 degrees left */
--analogous-2:   hsl(240, 80%, 50%);   /* purple - 30 degrees right */
--complementary: hsl(30, 80%, 50%);    /* orange - 180 degrees opposite */

The saturation and lightness stay identical, so the colors feel like a cohesive family. This is exactly how design systems like Tailwind CSS and Material Design generate their color scales programmatically.

Practical CSS Examples

CSS custom properties with HSL components

One of the most powerful HSL patterns is splitting the three values into separate CSS custom properties. This lets you reassemble them anywhere and tweak individual channels on the fly:

:root {
  --brand-h: 210;
  --brand-s: 80%;
  --brand-l: 50%;
  --brand-color: hsl(var(--brand-h), var(--brand-s), var(--brand-l));
}

.button {
  background-color: var(--brand-color);
}

.button:hover {
  /* Just override lightness - no need to redefine the whole color */
  background-color: hsl(var(--brand-h), var(--brand-s), 40%);
}

.button:disabled {
  background-color: hsl(var(--brand-h), 20%, 70%);
}
Note on syntax: When using CSS custom properties inside hsl() , you need commas: hsl(var(--h), var(--s), var(--l)) . The comma-free modern syntax does not work with var() inside hsl() in all browsers yet.

Generating a full tonal scale

Design systems often need 9-10 shades of a single color (like Tailwind's 50 through 950 scale). With HSL, you can generate the entire scale by stepping lightness at regular intervals while keeping hue and saturation fixed:

:root {
  --blue-50:  hsl(210, 80%, 95%);
  --blue-100: hsl(210, 80%, 87%);
  --blue-200: hsl(210, 80%, 76%);
  --blue-300: hsl(210, 80%, 65%);
  --blue-400: hsl(210, 80%, 55%);
  --blue-500: hsl(210, 80%, 50%);  /* base */
  --blue-600: hsl(210, 80%, 43%);
  --blue-700: hsl(210, 80%, 36%);
  --blue-800: hsl(210, 80%, 26%);
  --blue-900: hsl(210, 80%, 16%);
}

For a deeper look at how color formats relate to each other, the HEX to RGB conversion guide covers the full picture of CSS color formats including when each one is the right tool.

Hover States, Theming, and Dark Mode

Hover and focus states

HSL makes hover states trivial. Instead of defining a completely separate color, you just shift the lightness:

.btn-primary {
  background: hsl(210, 80%, 50%);
  transition: background 0.2s ease;
}

.btn-primary:hover  { background: hsl(210, 80%, 43%); }
.btn-primary:active { background: hsl(210, 80%, 36%); }
.btn-primary:focus-visible {
  outline: 3px solid hsl(210, 80%, 70%);
}

Every state is clearly related to the base color. A colleague reading this code immediately understands the relationship between states.

Theming with HSL variables

HSL is the backbone of modern CSS theming. By exposing just the hue as a variable, you can let users or admins change the entire color scheme of an app by changing one number:

/* Default theme: blue */
:root {
  --theme-hue: 210;
}

/* Green theme - just swap the hue */
[data-theme="green"] {
  --theme-hue: 140;
}

/* Purple theme */
[data-theme="purple"] {
  --theme-hue: 270;
}

/* All components use the same hue variable */
.button      { background: hsl(var(--theme-hue), 75%, 50%); }
.link        { color: hsl(var(--theme-hue), 75%, 40%); }
.badge       { background: hsl(var(--theme-hue), 75%, 92%); color: hsl(var(--theme-hue), 75%, 25%); }
.focus-ring  { outline-color: hsl(var(--theme-hue), 75%, 65%); }

Dark mode with HSL

Dark mode is where HSL really pulls ahead. Instead of maintaining two completely separate color palettes, you flip lightness values within the same hue:

:root {
  --bg:   hsl(210, 20%, 98%);   /* near-white background */
  --text: hsl(210, 20%, 15%);   /* near-black text */
  --card: hsl(210, 20%, 93%);   /* slightly darker card */
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:   hsl(210, 20%, 10%);  /* flip: near-black background */
    --text: hsl(210, 20%, 90%);  /* flip: near-white text */
    --card: hsl(210, 20%, 15%);  /* slightly lighter card in dark */
  }
}

Notice that the hue (210) and saturation (20%) never change. Only lightness flips. This keeps the dark mode feeling like the same design, just inverted - which is exactly what good dark mode should do.

Browser Support and Compatibility

HSL has excellent browser support. The hsl() function has been available since:

  • Chrome 1 (2008)
  • Firefox 1 (2004)
  • Safari 3.1 (2008)
  • Internet Explorer 9 (2011)
  • Edge 12 (2015)

The modern comma-free syntax ( hsl(210 80% 50%) ) and the slash-alpha syntax ( hsl(210 80% 50% / 0.5) ) are part of the CSS Color Level 4 specification and are supported in all modern browsers as of 2023. If you need IE11 support (rare at this point), stick to the comma syntax.

Safe to use today: hsl() with commas works in every browser your users are likely running. The modern space-separated syntax works in Chrome 90+, Firefox 89+, and Safari 14.1+, covering well over 95% of global browser usage.

You can always use a color picker to convert between HSL, RGB, and HEX formats when you need to cross-reference values or hand off colors to tools that only accept one format.

HSL vs OKLCH - What's Next?

If you want to go deeper than HSL, OKLCH is worth knowing about. It is a perceptually uniform color space, meaning that equal numeric steps in lightness or chroma actually look like equal steps to the human eye - something HSL does not fully guarantee.

The problem with HSL is that two colors with the same lightness value can look very different in perceived brightness. For example, hsl(60, 100%, 50%) (yellow) looks much brighter than hsl(240, 100%, 50%) (blue), even though both have 50% lightness. OKLCH corrects for this.

/* HSL - same lightness, different perceived brightness */
color: hsl(60, 100%, 50%);   /* yellow - looks very bright */
color: hsl(240, 100%, 50%);  /* blue - looks much darker */

/* OKLCH - same lightness, actually looks the same to the eye */
color: oklch(0.75 0.18 90);   /* yellow-ish */
color: oklch(0.75 0.18 260);  /* blue-ish - genuinely similar perceived brightness */

OKLCH also supports wide-gamut colors (P3 display colors) that go beyond the sRGB range HSL is limited to. Browser support for oklch() is solid in 2024: Chrome 111+, Firefox 113+, Safari 15.4+.

For most projects right now, HSL is the practical sweet spot: readable, maintainable, universally supported, and a massive improvement over RGB. OKLCH is the right choice when you are building a design system that needs mathematically consistent contrast ratios or you want to target wide-gamut displays.

If you are exploring color relationships visually while building your palette, the color explorer tool lets you experiment with HSL values and see how hue rotations, saturation shifts, and lightness changes interact in real time.

HSL color picker tool showing hue saturation lightness sliders

Pick HSL colors without the guesswork

Our color picker lets you dial in hue, saturation, and lightness values visually, then copy the exact HSL color model output straight into your CSS - no mental math required.

Try the Color Picker →

You do not need to do a full rewrite, but it is worth switching to HSL for any new color definitions you write, especially when using CSS custom properties. The biggest gains come when you are building or maintaining a design system, creating hover states, or implementing dark mode. Mixing RGB and HSL in the same project is perfectly valid CSS - browsers handle both without any performance difference.

No, there is no meaningful performance difference. The browser converts all CSS color formats to an internal representation at parse time, so whether you write hsl(207, 44%, 49%) or rgb(70, 130, 180) , the rendering engine treats them identically after that initial parse step. The choice is entirely about developer experience and maintainability, not runtime performance.

Yes, and this is one of HSL's strongest use cases in JavaScript. You can store hue, saturation, and lightness as separate numbers, then construct the color string dynamically: element.style.color = `hsl(${hue}, ${saturation}%, ${lightness}%)` . This makes animations, theme switching, and interactive color controls much simpler to implement than working with RGB channels, where you would need conversion math to achieve the same effects.

HSL (hue, saturation, lightness) and HSB/HSV (hue, saturation, brightness/value) are different color models even though they share the hue axis. In HSL, a lightness of 50% gives you the pure, fully saturated color. In HSB, a brightness of 100% gives you the pure color. The two models produce different results for the same saturation and brightness/lightness values. CSS uses HSL specifically - HSB/HSV is common in design tools like Photoshop and Figma but is not a native CSS format.

The conversion from HEX to HSL goes through an intermediate RGB step: first convert the HEX pairs to RGB values (0-255), normalize them to 0-1, then apply the HSL conversion formula. In practice, most developers use a color picker tool, a design tool like Figma, or a browser DevTools color picker to do this instantly rather than calculating by hand. Browser DevTools let you click any color swatch and cycle through HEX, RGB, and HSL representations.

Yes, OKLCH is more reliable for accessibility work because it is perceptually uniform - equal steps in its lightness channel correspond to equal perceived brightness changes. This makes it easier to build color palettes where contrast ratios are predictable across different hues. With HSL, yellow at 50% lightness looks much brighter than blue at 50% lightness, which can lead to accessibility surprises. For WCAG contrast compliance, OKLCH gives you more predictable results when building accessible color scales.