developer · · Updated

HEX, RGB and HSL: How the Conversions Work and Where They Round

By

HEX, RGB and HSL are three ways of writing the same sRGB colour, and CSS accepts all three. Converting between them is exact arithmetic, but copying the results is not always exact, and one of the three hides a trap about brightness. This guide works one colour through all three, then measures what rounding does.

One colour, three ways

Take #4F46E5, the indigo used in several examples on this site.

HEX to RGB. Split the six digits into pairs and read each pair as a base-16 number: 4F = 79, 46 = 70, E5 = 229. So #4F46E5 is rgb(79, 70, 229). To check a pair by hand, multiply the first digit by 16 and add the second: 4F = 4 × 16 + 15 = 79.

RGB to HSL takes a few more steps:

  1. Scale each channel to 0–1: r = 79/255 = 0.310, g = 70/255 = 0.275, b = 229/255 = 0.898.
  2. Find the largest and smallest: max = 0.898 (blue), min = 0.275.
  3. Lightness is their average: (0.898 + 0.275) / 2 = 0.586, or 59%.
  4. Saturation is the spread relative to how far lightness is from the middle: (0.898 − 0.275) / (1 − |2 × 0.586 − 1|) = 0.624 / 0.827 = 0.754, or 75%.
  5. Hue depends on which channel is largest. For blue, hue = 60 × ((r − g) / (max − min) + 4) = 60 × (0.035 / 0.624 + 4) = 243°.

So #4F46E5 is hsl(243, 75%, 59%), which is what the Color Picker shows.

The rounding trap

HSL is usually written with whole numbers, and that rounding changes the colour. Converting hsl(243, 75%, 59%) back to RGB gives #5048E5, not #4F46E5: red and green are each off by one or two steps out of 255.

This isn't one unlucky colour. Every one of the 16,777,216 RGB colours was put through the same round trip: RGB → HSL rounded to whole numbers → back to RGB.

  • 89.4% came back as a different colour.
  • For 54.4% of colours the biggest change in any one channel was a single step, for 32.1% it was two, for 2.9% three, and the worst case was five.

A difference of one or two steps is almost never visible on screen. It does matter when a colour has to match exactly: brand guidelines, a design token checked in tests, or a colour that has to equal a value somewhere else in code. Copy HEX or RGB when the exact value matters, and use HSL when you want to adjust a colour. The same trap applies to any tool that shows rounded HSL, not just this one.

HSL lightness is not brightness

HSL's lightness is a geometric midpoint, not how bright a colour looks. Pure yellow hsl(60, 100%, 50%) and pure blue hsl(240, 100%, 50%) have the same 50% lightness, but:

ColourContrast against whiteContrast against black
#FFFF00 (yellow)1.07 : 119.56 : 1
#0000FF (blue)8.59 : 12.44 : 1
#00FF00 (green)1.37 : 115.30 : 1
#FF0000 (red)4.00 : 15.25 : 1

White text on the yellow is unreadable, and on the blue it passes easily, at the same "lightness". So HSL is good for making lighter and darker versions of *one* hue, but a palette built by holding L fixed across hues won't look evenly bright, and it won't pass contrast checks evenly either. For that, CSS now has oklch(), whose lightness is designed to match perception; the CSS gradient guide uses it to keep a blue-to-yellow gradient from going grey in the middle.

Transparency, and the alpha hex can't write

Current CSS writes transparency with a slash: rgb(79 70 229 / 50%) or hsl(243 75% 59% / 0.5). The older rgba() and hsla() still work and mean the same thing.

Hex has an eight-digit form where the last pair is alpha: #4F46E580. The alpha pair is also out of 255, so exactly 50% isn't possible: 80 (128) is 50.2% and 7F (127) is 49.8%. When transparency needs to be exact, or needs to be readable at a glance, use the RGB or HSL form.

Which to use when

  • HEX for exact values you copy between tools: design files, brand guides, tokens.
  • RGB when code does arithmetic on channels: canvas drawing, image processing, blending.
  • HSL for adjusting a colour you already have: a hover state 8% darker, a tint for a background.
  • OKLCH when several colours need to look equally bright.

The Color Palette Generator builds five-colour sets from harmony rules and exports them as CSS variables, a Tailwind config or JSON, in HEX.

Related guides