β 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:
- Hue: 0-360Β°. The color wheel. 0Β° = red, 120Β° = green, 240Β° = blue.
- Saturation: 0-100%. 0% = gray, 100% = pure color.
- Lightness: 0-100%. 0% = black, 50% = pure color, 100% = white.
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 (Β°) | Color | RGB at S=100, L=50 |
|---|---|---|
| 0 | Red | rgb(255, 0, 0) |
| 30 | Orange | rgb(255, 128, 0) |
| 60 | Yellow | rgb(255, 255, 0) |
| 120 | Green | rgb(0, 255, 0) |
| 180 | Cyan | rgb(0, 255, 255) |
| 240 | Blue | rgb(0, 0, 255) |
| 270 | Purple | rgb(128, 0, 255) |
| 300 | Magenta | rgb(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
- Hex β RGB: do it in your head. Two hex digits per channel.
- RGB β Hex: divide by 16, write quotient and remainder as hex digits.
- HSL β palette variations: same H, vary L for shades; vary S for muted tones.
- HSL β RGB: offload to a tool. The math is correct but tedious.
- Contrast: never eyeball. Always run the WCAG ratio.
Tools
- Color Converter β HEX β RGB β HSL β HSV β named colors
- Contrast Checker β WCAG AA/AAA pass/fail for any pair
- Gradient Generator β CSS gradients between any two colors
- CSS Filter Generator β invert, hue-rotate, saturate without changing the source
- CSS Shadow Generator β pair shadow color with base color via HSL