← Blog Β· May 23, 2026 Β· dev, design

Color Math: Convert HEX, RGB, and HSL in Your Head

HEX and RGB are the same numbers in different bases β€” #FF8040 is justrgb(255, 128, 64) with each channel written as 2 hex digits. HSL is a completely different model (hue 0-360Β°, saturation 0-100%, lightness 0-100%) and there is no shortcut to convert it in your head β€” but you can learn to read it. This post covers the math worth memorizing and the math worth offloading to a tool.

HEX β†’ RGB: trivial, do it in your head

Each pair of hex digits is one channel, value 0-255. Hex digit values: 0-9 = 0-9,A-F = 10-15. A 2-digit hex value is high Γ— 16 + low.

#FF8040
 β”œβ”€β”€ FF = 15*16 + 15 = 255
 β”œβ”€β”€ 80 =  8*16 +  0 = 128
 └── 40 =  4*16 +  0 =  64
        = rgb(255, 128, 64)

Memorize these six pairs and you can read any hex color: 00=0, 33=51,66=102, 99=153, CC=204, FF=255. Those are the Tailwind/Web-safe steps and they cover most palette work.

Short-form hex β€” the doubling trick

#FAB is shorthand for #FFAABB β€” each digit is doubled. So#F00 = #FF0000 = pure red. #ABC = #AABBCC. Short form only works when each channel is two of the same digit, so #A1Bcannot be written shorter than #AA11BB.

RGB β†’ HEX: split into 16s

128 / 16 = 8 remainder 0  β†’ "80"
255 / 16 = 15 remainder 15 β†’ "FF"
200 / 16 = 12 remainder 8  β†’ "C8"

JavaScript:
const hex = n => n.toString(16).padStart(2, '0').toUpperCase();
`#${hex(r)}${hex(g)}${hex(b)}`;

HSL β€” a different mental model

HSL is not a re-encoding of RGB. It is a cylindrical model designed for human reasoning:

HSL is what you want when designing a palette. To make a button hover state, drop the L by 5-10%. To make a disabled state, drop the S by 50%. To shift accent color, change only H. Try this in our color converter β€” drag the L slider and watch the RGB jump unpredictably.

The hue keypoints worth memorizing

Hue (Β°)ColorRGB at S=100, L=50
0Redrgb(255, 0, 0)
30Orangergb(255, 128, 0)
60Yellowrgb(255, 255, 0)
120Greenrgb(0, 255, 0)
180Cyanrgb(0, 255, 255)
240Bluergb(0, 0, 255)
270Purplergb(128, 0, 255)
300Magentargb(255, 0, 255)

HSL β†’ RGB: the actual math

Don't try this in your head β€” read it once, then offload to a tool. But knowing the shape of the math helps you predict edge cases.

function hslToRgb(h: number, s: number, l: number) {
  s /= 100; l /= 100;
  const c = (1 - Math.abs(2 * l - 1)) * s;       // chroma
  const x = c * (1 - Math.abs((h / 60) % 2 - 1));// secondary
  const m = l - c / 2;                            // lightness offset
  let [r, g, b] = [0, 0, 0];
  if      (h <  60) [r,g,b] = [c, x, 0];
  else if (h < 120) [r,g,b] = [x, c, 0];
  else if (h < 180) [r,g,b] = [0, c, x];
  else if (h < 240) [r,g,b] = [0, x, c];
  else if (h < 300) [r,g,b] = [x, 0, c];
  else              [r,g,b] = [c, 0, x];
  return [r, g, b].map(v => Math.round((v + m) * 255));
}

The model splits the color wheel into six 60Β° sectors and computes RGB from chroma and offset. The chroma drops as lightness moves away from 50% β€” that is why super-light or super-dark HSL colors lose saturation in practice.

The contrast ratio formula β€” what WCAG actually checks

WCAG 2.x contrast is not a simple subtraction of brightness. It uses relative luminance (Y) on linearized sRGB:

1. Linearize each channel:
   v = c/255
   v_lin = v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4

2. Compute luminance:
   Y = 0.2126 * R_lin + 0.7152 * G_lin + 0.0722 * B_lin

3. Contrast ratio:
   ratio = (Y_lighter + 0.05) / (Y_darker + 0.05)

WCAG AA targets:
  Normal text: ratio >= 4.5
  Large text:  ratio >= 3.0

Green dominates luminance (71.5%) because the human eye is most sensitive to it. That is why pure yellow (R+G) looks much brighter than pure cyan (G+B) at equal RGB intensity. Check pairs with our contrast checker β€” it gives the ratio and the AA/AAA pass/fail.

The conversion shortcuts worth memorizing

  1. Hex β†’ RGB: do it in your head. Two hex digits per channel.
  2. RGB β†’ Hex: divide by 16, write quotient and remainder as hex digits.
  3. HSL β†’ palette variations: same H, vary L for shades; vary S for muted tones.
  4. HSL ↔ RGB: offload to a tool. The math is correct but tedious.
  5. Contrast: never eyeball. Always run the WCAG ratio.

Tools

← All blog posts