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

FormatStrengthWeakness
HEXCompact in tokens, diffs, and handoffsHard to mentally lighten/d darken
RGBMatches design spec sheets (0–255)Verbose; hue relationships opaque
HSLTheme 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:

  1. Designers author in Figma (often hex or RGB)
  2. Engineers store hex in tokens
  3. Build scripts emit HSL for theme generators or chart APIs
  4. 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.

Related tools

Use the tools from this article

Color Picker & Convertercolor picker / hex / rgb

Learn the format

Color for Developers CourseLearn color models for UI work: HEX, RGB, HSL, WCAG contrast, CSS formats, and a practical picking workflow.

Back to articles