X Xerobit

HSL Color — Hue, Saturation, and Lightness Explained

HSL represents colors as hue (0–360°), saturation (0–100%), and lightness (0–100%). It's more intuitive than RGB for programmatic color manipulation — here's how it works and...

Mian Ali Khalid · · 5 min read
Use the tool
Color Picker
Pick colors, convert hex/RGB/HSL/OKLCH, and check WCAG contrast.
Open Color Picker →

HSL (Hue, Saturation, Lightness) represents colors using three perceptual dimensions that map to how humans think about color. Instead of RGB channels, you specify the color angle on a color wheel (hue), the intensity of color (saturation), and how light or dark it is (lightness).

Use the Color Picker to convert between HSL, RGB, hex, and other color formats.

The three dimensions of HSL

Hue (0–360°)

Hue is the color angle on a color wheel:

0°   = Red
30°  = Orange
60°  = Yellow
90°  = Yellow-green
120° = Green
150° = Cyan-green
180° = Cyan
210° = Cyan-blue
240° = Blue
270° = Blue-violet
300° = Magenta
330° = Pink
360° = Red (same as 0°)

The wheel wraps: 0° and 360° are the same red.

Saturation (0–100%)

Saturation controls color intensity:

  • 100% — fully saturated, vivid color
  • 50% — muted, grayish
  • 0% — no color, pure gray (the hue doesn’t matter)

At 0% saturation, all hues become gray. The gray’s lightness is determined by the lightness value.

Lightness (0–100%)

Lightness controls how light or dark:

  • 100% — pure white (hue and saturation don’t matter)
  • 50% — the “natural” brightness of the color
  • 0% — pure black (hue and saturation don’t matter)

Most vivid colors are around 50% lightness. Lighter versions approach white (75–90%); darker versions approach black (10–25%).

HSL vs RGB for code

RGB (rgb(255, 99, 71)) stores the color as three channel values (0–255 each). It’s direct hardware representation but not intuitive to modify:

  • To make a color 20% lighter, you don’t know which channels to adjust
  • To create a darker variant of a color, you’d need to calculate new channel values

HSL makes color manipulation intuitive:

/* Original color: hsl(9, 100%, 64%) */
.tomato { color: hsl(9, 100%, 64%); }

/* 20% lighter: just increase lightness */
.tomato-light { color: hsl(9, 100%, 84%); }

/* 20% darker: */
.tomato-dark { color: hsl(9, 100%, 44%); }

/* Muted version: decrease saturation */
.tomato-muted { color: hsl(9, 40%, 64%); }

/* Complementary color: rotate hue 180° */
.tomato-complementary { color: hsl(189, 100%, 64%); }

This is the main advantage of HSL for design systems.

CSS HSL syntax

/* Basic: */
color: hsl(120, 100%, 50%);  /* Pure green */

/* With alpha (transparency): */
color: hsl(120 100% 50% / 0.5);  /* 50% transparent green */
color: hsla(120, 100%, 50%, 0.5); /* Same, older syntax */

/* Variables: */
:root {
  --primary-hue: 220;
  --primary-sat: 90%;
}

.button {
  background: hsl(var(--primary-hue), var(--primary-sat), 50%);
}
.button:hover {
  background: hsl(var(--primary-hue), var(--primary-sat), 40%);
}

Building a color scale with HSL

A common design pattern: define one hue, vary lightness for a full color scale:

:root {
  --blue-hue: 220;
  --blue-sat: 90%;
  
  --blue-50:  hsl(var(--blue-hue), var(--blue-sat), 97%);
  --blue-100: hsl(var(--blue-hue), var(--blue-sat), 94%);
  --blue-200: hsl(var(--blue-hue), var(--blue-sat), 85%);
  --blue-300: hsl(var(--blue-hue), var(--blue-sat), 74%);
  --blue-400: hsl(var(--blue-hue), var(--blue-sat), 60%);
  --blue-500: hsl(var(--blue-hue), var(--blue-sat), 50%);  /* Base */
  --blue-600: hsl(var(--blue-hue), var(--blue-sat), 40%);
  --blue-700: hsl(var(--blue-hue), var(--blue-sat), 30%);
  --blue-800: hsl(var(--blue-hue), var(--blue-sat), 20%);
  --blue-900: hsl(var(--blue-hue), var(--blue-sat), 10%);
}

This is how Tailwind CSS generates its color scales.

Converting HSL to RGB

The formula (for implementation):

Given: H (0–360), S (0–1), L (0–1)

C = (1 - |2L - 1|) × S
X = C × (1 - |H/60 mod 2 - 1|)
m = L - C/2

Then map (H/60) to determine (R1, G1, B1):
0–1: (C, X, 0)
1–2: (X, C, 0)
2–3: (0, C, X)
3–4: (0, X, C)
4–5: (X, 0, C)
5–6: (C, 0, X)

(R, G, B) = (R1+m, G1+m, B1+m) × 255

In practice, use a library:

// CSS parses HSL natively — no need to convert manually in CSS
// In JavaScript (for canvas/WebGL):
function hslToRgb(h, s, l) {
  s /= 100; l /= 100;
  const a = s * Math.min(l, 1 - l);
  const f = n => {
    const k = (n + h / 30) % 12;
    return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
  };
  return [Math.round(f(0)*255), Math.round(f(8)*255), Math.round(f(4)*255)];
}

console.log(hslToRgb(9, 100, 64)); // [255, 99, 71] (Tomato)

HSL limitations

Perceptual non-uniformity: Equal steps in HSL lightness don’t look equal to human eyes across different hues. Yellow at 50% lightness looks much lighter than blue at 50% lightness. This is an inherent HSL limitation.

Better alternative: OKLCH (CSS Color Level 4, modern browsers) is perceptually uniform — equal steps in lightness actually look equal across all hues. Use OKLCH for design systems that need consistent visual weight.

/* OKLCH: perceptually uniform, but less browser support */
color: oklch(68% 0.18 29);  /* Same Tomato color */

For most web development, HSL is sufficient. For high-quality design systems targeting modern browsers, consider OKLCH.


Related posts

Related tool

Color Picker

Pick colors, convert hex/RGB/HSL/OKLCH, and check WCAG contrast.

Written by Mian Ali Khalid. Part of the Frontend & Design pillar.