HEX, RGB, HSL, and HSV: a developer's complete guide to colour formats
What each colour format means, how to convert between them, when to use each in CSS, and the most common bugs caused by confusing colour spaces.
CSS supports over a dozen ways to specify a colour. HEX, RGB, HSL, HSV, HWB, LAB, LCH, OKLCH, named colours, and more. Most developers learn HEX first, use RGB when they need alpha, and never think about HSL. This is a mistake — HSL is by far the most intuitive format for working with colour programmatically, and understanding the differences between formats will save you hours of debugging mismatched colours and failed conversions.
HEX: what the digits mean
A HEX colour is three bytes — one each for red, green, and blue — written in hexadecimal notation and prefixed with #. Each byte ranges from 00 (0 in decimal, no contribution) to FF (255 in decimal, full contribution).
#FF5733
^^ Red = 0xFF = 255 (full red)
^^ Green = 0x57 = 87 (some green)
^^ Blue = 0x33 = 51 (a little blue)
Result: a warm orange-redThe 3-digit shorthand #RGB is equivalent to #RRGGBB where each digit is doubled — so #F53 means #FF5533. This only works when both hex digits of each channel are identical.
The 8-digit form #RRGGBBAA adds an alpha channel (opacity). #FF573380 is the same orange-red at 50% opacity (0x80 = 128 / 255 ≈ 0.502). Browser support for 8-digit HEX is universal in 2026.
RGB: the additive model
RGB describes colour by the amounts of red, green, and blue light combined. In CSS, channels are 0–255 integers or 0%–100% percentages. The rgba() function adds an alpha channel as a 0–1 decimal or percentage.
/* All equivalent */ color: rgb(255, 87, 51); color: rgba(255, 87, 51, 1); color: rgb(100% 34.1% 20%); /* 50% transparent */ color: rgba(255, 87, 51, 0.5); color: rgb(255 87 51 / 50%); /* modern syntax */
Converting between HEX and RGB is arithmetic: divide each HEX byte by 255 to get a 0–1 float, or multiply by 255 to go back. Use the Number Base Converter to translate individual hex bytes to decimal if you are doing this manually.
HSL: the human-friendly model
HSL (Hue, Saturation, Lightness) describes colour the way humans think about it. Instead of asking "how much red, green, and blue?" it asks "what colour is it, how vivid is it, and how light or dark?"
- Hue (0–360°) — the colour wheel. 0° and 360° are red. 120° is green. 240° is blue. 60° is yellow. 180° is cyan. 300° is magenta.
- Saturation (0–100%) — how vivid the colour is. 0% is grey (no colour at all), 100% is the purest version of the hue.
- Lightness (0–100%) — how light or dark. 0% is always black regardless of hue, 50% is the "pure" colour, 100% is always white.
hsl(11, 100%, 60%) /* our warm orange-red */ hsl(11, 50%, 60%) /* same hue, less vivid (pastel) */ hsl(11, 100%, 80%) /* same hue, lighter */ hsl(191, 100%, 60%) /* complementary blue (11 + 180 = 191) */
HSL's superpower is that relationships between colours are easy to compute. The complementary colour of any hue is hue + 180. An analogous palette is hue ± 30. A lighter/darker variant is just a lightness adjustment. None of this is intuitive in HEX or RGB.
HSV / HSB: the Photoshop model
HSV (Hue, Saturation, Value) is sometimes called HSB (Hue, Saturation, Brightness). It is the colour model used in Photoshop, Illustrator, Figma, and most design tools. The hue and saturation axes work the same as HSL, but "value" behaves differently from "lightness":
- HSL lightness 50% is the pure, saturated colour. Increasing lightness adds white; decreasing adds black. At 100%, you always get white.
- HSV value 100% is the pure, saturated colour. Decreasing value adds black. You can never reach white by adjusting value alone — white in HSV requires 0% saturation + 100% value.
In practice: HSV is better for a colour picker where you separately control vividity and brightness. HSL is better for CSS because its 50% midpoint at full saturation maps directly to the "pure" colour. CSS uses HSL (and the newer OKLCH), not HSV.
Conversion formulas (and why to use a tool)
HEX ↔ RGB is trivial arithmetic. RGB ↔ HSL and RGB ↔ HSV are more involved — they require normalising to 0–1, finding min/max channel values, and handling multiple cases depending on which channel is dominant. The Color Converter handles all conversions (HEX ↔ RGB ↔ HSL ↔ HSV) without the arithmetic errors that come from doing it manually.
// RGB to HSL in JavaScript (correct, handling all edge cases)
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) { h = s = 0; } // achromatic
else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
}When to use each format in CSS
| Format | Best for | Avoid when |
|---|---|---|
| HEX | Design tokens, copying from Figma/brand guidelines | You need to compute colour variations programmatically |
| RGB | When you need alpha transparency inline | Theming / colour scales (hard to reason about) |
| HSL | Design systems, CSS custom properties, hover states, dark mode | Matching exact HEX values from a brand palette |
| OKLCH | Perceptually uniform scales, wide-gamut (P3) displays | Supporting very old browsers (IE, legacy Safari) |
Common bugs
- HSL lightness ≠ perceived brightness: HSL 50% lightness looks very different across hues — yellow at 50% looks much brighter than blue at 50% even though they have the same L value. OKLCH fixes this with perceptual uniformity; use it for accessible colour contrast ratios.
- Alpha in HEX vs CSS:
#RRGGBBAAuses a hex byte (0x00–0xFF) for alpha.rgba()uses 0–1 or a percentage. Copying an 8-digit HEX alpha directly intorgba()as a decimal is wrong — divide by 255 first. - CSS hue units:
hsl(180deg, 100%, 50%)andhsl(180, 100%, 50%)are both valid.hsl(0.5turn, 100%, 50%)is also valid (half a turn = 180°). Mixing unit systems in calculations leads to off-by-factor-of-360 bugs.
Try it now
Convert any colour between HEX, RGB, HSL, and HSV instantly at quickhelp.dev/color-converter. Paste a HEX value, an rgb() string, or an hsl() value and get all four representations at once. The tool also works as an API: POST /api/color-converter with {"color":"#FF5733","from":"hex","to":"hsl"}. For converting individual hex byte values to decimal, see the Number Base Converter.