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...
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 tools
- Color Picker — pick colors and convert between all formats
- Hex Color Codes — when to use hex vs HSL
- WCAG Contrast — accessible color combinations
Related posts
- WCAG Contrast Explained (AA vs AAA, When It Matters) — Color contrast determines who can read your interface. This is the WCAG math, th…
- Hex, RGB, HSL, OKLCH: Which to Pick in 2026 — Four CSS color formats, four different audiences. This is what each is good at, …
- Color Accessibility — WCAG Contrast Ratios and Tools — WCAG 2.1 requires a 4.5:1 contrast ratio for normal text and 3:1 for large text.…
- Hex Color Codes Explained — RGB, HSL, and How Colors Work in CSS — Hex color codes are 6-digit hexadecimal numbers that represent RGB values. Here'…
- RGB to Hex Converter — Convert Colors Between RGB and Hex — RGB to hex conversion is a two-step process: convert each of the three channels …
Related tool
Pick colors, convert hex/RGB/HSL/OKLCH, and check WCAG contrast.
Written by Mian Ali Khalid. Part of the Frontend & Design pillar.