HEX vs RGB vs HSL for CSS and Design Tokens
When to store hex, rgb(), or hsl() in design systems, how alpha and rounding interact, and how WCAG contrast fits the pipeline.
The same brand blue arrives as #2563EB, rgb(37, 99, 235), and hsl(221, 83%, 53%). All three can be correct—and all three can drift apart after rounding, alpha compositing, or a designer tweak in Figma.
This comparison helps frontend and design-system engineers pick a canonical storage format and derive the others without breaking contrast checks.
What each format optimizes for
| Format | Strength | Weakness |
|---|---|---|
| HEX | Compact in tokens, diffs, and handoffs | Hard to mentally lighten/d darken |
| RGB | Matches design spec sheets (0–255) | Verbose; hue relationships opaque |
| HSL | Theme tweaks (same hue, new lightness) | Not perceptually uniform; contrast surprises |
None of the three is "more accurate"—they are views of the same sRGB triple unless you move to wider spaces like OKLCH.
Pick one source of truth per token
Healthy design systems store one authoritative value per semantic token (--color-primary) and generate platform outputs:
:root {
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-on-primary: #ffffff;
}
Components reference --color-on-primary, not scattered hex literals. When marketing sends an updated swatch, update the token once and rebuild derived HSL/RGB for docs.
Use the Color Converter to verify conversions and WCAG contrast for text pairs—not only the swatch on white.
Alpha compositing changes contrast
rgba(37, 99, 235, 0.12) on a white card is not the same pair as #2563EB text on solid blue. WCAG checks must use composited foreground/background on the surface users actually see.
Semi-transparent overlays on photos fail more often than flat fills—test the worst background, not the Figma artboard default.
HSL for programmatic palettes, hex for delivery
A common pipeline:
- Designers author in Figma (often hex or RGB)
- Engineers store hex in tokens
- Build scripts emit HSL for theme generators or chart APIs
- Email/PDF exports get inline hex because
hsl()support is inconsistent
If you generate hover states by subtracting HSL lightness, re-run contrast on the new pair—"10% darker" is not guaranteed AA.
Rounding and round-trip drift
HEX → HSL → HEX may not return identical bytes. That matters when:
- Comparing CI snapshots of CSS bundles
- Syncing Android XML colors with web tokens
Document rounding rules in the token repo README. Do not let each app round independently.
Quick decision guide
- Need compact git diffs and universal tool support? → Canonical hex
- Integrating canvas/WebGL/chart APIs? → Derive RGB at build time
- Building light/dark themes from one hue ramp? → Author adjustments in HSL or OKLCH, verify contrast after each step
- Shipping HTML email? → Hex only in templates
Related learning
For WCAG ratios, CSS Color Level 4 syntax, and a sampling workflow, see the Color for Developers course.
Bottom line
HEX, RGB, and HSL are interchangeable views—not competing religions. Choose one canonical form, derive the rest deliberately, and measure contrast on real backgrounds. That is how #2563EB stays one blue in production instead of five.