A small frontend color dilemma
Given the requirement "make the main color a bit softer", you'd be stuck inside RGB—touching R drifts the hue, changing G/B can't say how much lighter things get. But switch to HSL and the answer becomes one number: drop saturation S by 20%. That is why understanding color models matters.
This article explains the three models most used on the frontend and lands on contrast and accessible color picking.
Reading the three color models
HEX = RGB in hex notation
#FF8800
├R=0xFF→255 ├G=0x88→136 └B=0x00→0
There's also the 3-digit shorthand #RGB: #f80 = #ff8800. It's the most compact CSS notation, but you can't eyeball lightness from it.
RGB: mixing three primaries
rgb(255, 136, 0)
R=255 G=136 B=0
Displays use this underneath; the numbers are literal but picking colors isn't intuitive.
HSL: splitting the feeling into three axes
hsl(30, 100%, 50%)
H=30°(orange) S=100% L=50%
- H hue 0–360°: red 0, yellow 60, green 120, cyan 180, blue 240, magenta 300;
- S saturation 0–100%: gray to vivid;
- L lightness 0–100%: black to white.
Conversion in a glance
| From → to | Mechanics |
|---|---|
| HEX → RGB | decode each 2 hex digits to decimal |
| RGB → HSL | normalize → max/min spread sets S, mean sets L, extreme position sets H |
| HSL → RGB | reconstruct by H interval |
Most pickers (color-picker) let you flip between HEX/RGB/HSL—handy for verifying conversions.
An accessibility-aware color workflow
Color design isn't only about aesthetics; contrast must meet thresholds. The full flow:
- Pick the primary: use a picker/palette to settle the brand color;
- Build neutrals: generate light/dark variants (same hue, different L/S);
- Compute contrast: compare each 'foreground text vs background' pair;
- Check WCAG: body ≥4.5 (AA) / large ≥3; stricter teams target AAA 7.0.
Contrast formula
L = 0.2126·R̄ + 0.7152·Ġ + 0.0722·B̄ (R̅/G̅/B̅ are linearized luminance)
contrast = (L_light + 0.05) / (L_dark + 0.05)
WCAG thresholds:
| Level | Normal text | Large/bold/UI components |
|---|---|---|
| AA | ≥ 4.5 | ≥ 3 |
| AAA | ≥ 7 | ≥ 4.5 |
A common pitfall: on the same background, different text lightness gives wildly different contrast. Test dark and light versions of the same hue separately—often only one passes.
How palette generators work
Many palette tools (color-palette-generator / color-picker) internally follow this logic:
- Pick a base hue H0;
- Generate analogous/complementary candidates at 60° intervals;
- For each candidate, produce a lightness gradient using "same H, varying S/L";
- Return a usable 10–12 color set with primary, neutral and accent colors.
That way the family is harmonious (hues are intrinsically related) yet contains high-contrast colors usable for text.
Self-check checklist
- [ ] You can convert between HEX ↔ RGB ↔ HSL by hand/tool for the same color;
- [ ] You can say what each of HSL's three axes controls, and explain why adjusting L is more predictable than adjusting RGB;
- [ ] You can compute contrast and recall the two AA/AAA thresholds for body and large text;
- [ ] You can confirm color values with a picker and quickly build a set of light/dark variants with a palette.
String conversion, picking, palette and contrast into one workflow, and choosing colors stops being a lottery.