Hex vs RGB vs HSL: Color Formats for Developers

8 minMay 18, 2026

Hex vs RGB vs HSL: What Each Format Represents

The hex vs rgb vs hsl debate isn't about which is "better" — they're three ways to write the same color. #FF6B35 and rgb(255, 107, 53) and hsl(16, 100%, 60%) are identical. The difference is how they decompose color into components, which affects how easy it is to read, modify, and generate colors programmatically.

Hex (#RRGGBB) packs red, green, and blue into 6 hexadecimal digits. Each pair ranges from 00 (none) to FF (maximum). It's compact and universal — every browser, design tool, and API understands hex. The downside: it's not human-readable. Can you tell that #2E86AB is a medium blue just by looking at it? Most people can't. Hex is for storage and communication, not for thinking about color.

RGB (red, green, blue) is the same as hex but in decimal (0-255 per channel). rgb(46, 134, 171) is slightly more readable than #2E86AB — you can see that blue (171) dominates, green (134) is moderate, and red (46) is low. But it's still hard to answer questions like "make this 20% lighter" or "shift this toward orange" because lightness and hue aren't explicit in RGB.

HSL (hue, saturation, lightness) separates color into three intuitive dimensions. Hue is the color wheel position (0°=red, 120°=green, 240°=blue). Saturation is intensity (0%=gray, 100%=vivid). Lightness is brightness (0%=black, 50%=pure color, 100%=white). Want a lighter version? Increase L. Want a muted version? Decrease S. Want a complementary color? Add 180° to H. This is why HSL wins for programmatic color manipulation.

When to Use Each Format (Practical Rules)

Use hex for: CSS variables, design tokens, brand color definitions, API responses, database storage. It's the most compact text representation (6-8 characters) and universally supported. When a designer hands you a color spec, it'll be in hex. When you store colors in a config file, use hex.

Use RGB for: canvas operations, WebGL, image processing, and any context where you're manipulating individual color channels. The Canvas API works in RGB. Blending two colors is straightforward in RGB (average each channel). Alpha compositing formulas use RGB values. If you're doing pixel-level work, think in RGB.

Use HSL for: generating color palettes, creating hover/active states, building themes, and any UI where users pick colors. "10% darker" is trivial in HSL (subtract 10 from L). "Same color but muted" is trivial (reduce S). Generating 5 evenly-spaced colors is trivial (divide 360° by 5 and use those hues). Our color-picker tool shows all three formats simultaneously so you can see the relationships.

Use OKLCH for: perceptually uniform color manipulation (new in CSS Color Level 4). HSL has a flaw — hsl(60, 100%, 50%) (yellow) looks much brighter than hsl(240, 100%, 50%) (blue) even though they have the same L value. OKLCH fixes this with perceptually uniform lightness. If you're building a design system in 2026 and need consistent perceived brightness across hues, OKLCH is the right choice. The syntax is oklch(lightness chroma hue) where lightness is 0-1, chroma is 0-0.4 (roughly), and hue is 0-360°.

/* Same color in different formats */
.button {
  /* All identical: a warm orange */
  color: #FF6B35;
  color: rgb(255, 107, 53);
  color: hsl(16, 100%, 60%);
  color: oklch(0.7 0.18 45);
}

/* HSL makes variations trivial */
.button:hover {
  /* 10% darker: just reduce lightness */
  background: hsl(16, 100%, 50%);
}
.button:active {
  /* 20% darker */
  background: hsl(16, 100%, 40%);
}
.button--muted {
  /* Same hue, less saturated */
  background: hsl(16, 40%, 60%);
}

/* Generating a palette with HSL */
:root {
  --primary: hsl(220, 70%, 50%);
  --primary-light: hsl(220, 70%, 70%);
  --primary-dark: hsl(220, 70%, 30%);
  --complement: hsl(40, 70%, 50%); /* +180° hue */
}

Color Manipulation in Code

Darken/lighten: In HSL, subtract/add to the L value. hsl(200, 80%, 50%) darkened by 20% becomes hsl(200, 80%, 30%). In hex/RGB, you'd need to convert to HSL first, modify, then convert back. This is why every CSS-in-JS library (styled-components, emotion) provides darken() and lighten() helpers that work in HSL internally.

Opacity/transparency: Add an alpha channel. Hex uses 8 digits (#FF6B3580 = 50% opacity). RGB becomes rgba(255, 107, 53, 0.5). HSL becomes hsla(16, 100%, 60%, 0.5). The alpha value ranges from 0 (fully transparent) to 1 (fully opaque). Note: 8-digit hex isn't supported in IE11 (if you still care), but rgba() works everywhere.

Color mixing: The simplest approach is linear interpolation in RGB. Mix 50% red and 50% blue: r=(255+0)/2=128, g=(0+0)/2=0, b=(0+255)/2=128 → rgb(128, 0, 128) = purple. CSS now has color-mix(): color-mix(in srgb, red 50%, blue) does this natively. For perceptually better mixing, interpolate in OKLCH space — RGB mixing can produce muddy intermediate colors.

Generating accessible color pairs: Calculate the WCAG contrast ratio between foreground and background. The formula uses relative luminance: L = 0.2126×R + 0.7152×G + 0.0722×B (where R, G, B are linearized from sRGB). Contrast ratio = (L1 + 0.05) / (L2 + 0.05) where L1 > L2. WCAG AA requires 4.5:1 for normal text, 3:1 for large text. Our color-picker tool calculates this automatically.

CSS Color Functions (Modern Approaches)

CSS relative colors (2024+): color: hsl(from var(--brand) h s calc(l - 20%)) creates a darker version of your brand color without JavaScript. The "from" keyword destructures the source color into its components, which you can then modify with calc(). This eliminates the need for preprocessor color functions in many cases.

color-mix() (supported since 2023): color-mix(in oklch, var(--primary) 70%, white) mixes 70% of your primary color with 30% white, producing a tint. The "in oklch" part specifies the color space for interpolation — oklch produces more natural-looking mixes than srgb for most color combinations.

The oklch() function: oklch(0.7 0.15 250) specifies lightness (0-1), chroma (0-0.4ish, how colorful), and hue (0-360°). Unlike HSL, equal lightness values in OKLCH actually look equally bright to human eyes. This matters for data visualization — if you need 5 colors that are equally prominent, OKLCH with the same L value achieves this while HSL doesn't.

Practical tip: define your design tokens in hex (for compatibility and designer handoff), but use HSL or OKLCH in your CSS for derived colors (hover states, disabled states, focus rings). This gives you the best of both worlds: stable reference colors that designers recognize, plus easy programmatic variations without JavaScript.

Accessibility and Contrast Ratios

WCAG 2.1 contrast requirements: 4.5:1 for normal text (under 18px or under 14px bold), 3:1 for large text (18px+ or 14px+ bold), 3:1 for UI components and graphical objects. These aren't suggestions — they're legal requirements in many jurisdictions (ADA in the US, EN 301 549 in the EU). Failing contrast means some users literally cannot read your content.

Common failures: light gray text on white (#999 on #fff = 2.85:1, fails AA), placeholder text that's too faint, disabled button text that's unreadable, colored links that don't contrast with surrounding text. The fix is usually simple — darken the text or lighten the background until you hit 4.5:1. Our color-picker tool shows the contrast ratio as you adjust colors.

Dark mode trap: inverting colors doesn't preserve contrast ratios. #333 on #fff (12.6:1) inverted to #ccc on #000 is only 13.1:1 — fine. But #666 on #fff (5.7:1) inverted to #999 on #000 is only 5.1:1 — still passes but barely. And some color combinations that pass in light mode fail in dark mode after inversion. Always check contrast ratios separately for each mode.

Beyond WCAG: approximately 8% of men and 0.5% of women have some form of color vision deficiency. Don't rely on color alone to convey information (red/green for error/success is the classic failure). Use icons, patterns, or text labels alongside color. Test your UI with a color blindness simulator — Chrome DevTools has one built in (Rendering panel → Emulate vision deficiencies).

Color Spaces Beyond sRGB

sRGB is the default color space for the web. It covers about 35% of visible colors. Modern displays (P3, used in iPhones since 2016 and MacBooks since 2015) can show about 25% more colors than sRGB. If you're only using hex/rgb, you're limited to sRGB. To access the wider gamut, use color(display-p3 1 0.5 0) in CSS.

When wider gamut matters: vibrant photography, brand colors that look "flat" in sRGB (especially saturated reds and greens), and any design where you want colors to "pop" on capable displays. When it doesn't matter: text, UI chrome, backgrounds, and anything where subtle color differences aren't important.

Fallback strategy: use @supports (color: color(display-p3 1 0 0)) to detect P3 support, and provide an sRGB fallback. Or use the color() function with a fallback: color: #ff6b35; color: color(display-p3 1 0.45 0.2); — browsers that don't understand display-p3 ignore the second declaration and use the hex value.

For developers who aren't designers: you probably don't need to think about wide gamut colors. sRGB covers all the colors in typical UI design. Wide gamut is relevant for photography sites, e-commerce product images, and brand-heavy marketing pages. If your designer hasn't mentioned P3 colors, stick with hex/rgb/hsl in sRGB and focus on getting contrast ratios right. The performance cost of wide gamut is zero — it's just a different color space declaration in CSS, not a rendering overhead.

Common Color Mistakes in Web Development

Mistake 1: Hardcoding colors instead of using CSS custom properties. If your brand blue appears in 47 places and the designer changes it, you're doing 47 find-and-replace operations. Define colors once as --color-primary: hsl(220, 70%, 50%) and reference them everywhere. This also makes dark mode trivial — just redefine the custom properties under a .dark class or media query.

Mistake 2: Using opacity for hover states instead of HSL lightness. Setting opacity: 0.8 on a colored button makes the background bleed through, creating an unpredictable color. Instead, use a slightly darker/lighter version of the same color: background: hsl(220, 70%, 45%) for hover (5% darker). This gives consistent results regardless of what's behind the element.

Mistake 3: Not testing colors on different monitors. A color that looks vibrant on your MacBook Pro (P3 gamut, high brightness) might look washed out on a cheap office monitor (sRGB, low contrast). Always test on at least one non-premium display. The colors that survive this test are the ones your users will actually see.

Mistake 4: Ignoring color in dark mode. Simply inverting colors or swapping black/white backgrounds produces harsh, eye-straining interfaces. Good dark mode uses slightly desaturated colors (reduce S by 10-20% in HSL), elevated surfaces with subtle gray differences (not pure black), and reduced contrast for large text areas (pure white on pure black is too harsh — use #e0e0e0 on #1a1a1a instead).