HEX vs RGB vs HSL: Which Color Format Should You Use?
All three traditional color formats — HEX, RGB, and HSL — represent the same sRGB color space. They can express exactly the same 16.7 million colors. The difference is not in capability, but in usability: how easy each format makes it to read, modify, and systematize colors in your codebase. Choosing the right format for the right context reduces cognitive load, simplifies theming, and makes your CSS more maintainable.
This guide compares HEX, RGB, HSL, and the newer OKLCH format with practical examples, conversion formulas, design system patterns, and recommendations for different development contexts.
Side-by-Side Comparison
The same color in all formats:
/* Indigo-500 */
color: #6366f1; /* HEX */
color: rgb(99, 102, 241); /* RGB */
color: hsl(239, 84%, 67%); /* HSL */
color: oklch(0.585 0.233 285); /* OKLCH — modern CSS */
Convert between formats instantly with our Color Converter.
Format Comparison Table
| Property | HEX | RGB | HSL | OKLCH |
|---|---|---|---|---|
| Human readability | 🔴 Low | 🟡 Medium | 🟢 High | 🟢 High |
| Creating variations | 🔴 Hard | 🟡 Requires all 3 values | 🟢 Adjust L only | 🟢 Adjust L only |
| Color math | 🔴 Hard | 🟢 Easy (0-255 integers) | 🟡 Medium | 🟢 Easy |
| Design tool export | 🟢 Universal | 🟢 Common | 🟡 Less common | 🔴 Rare |
| Perceptual uniformity | ❌ | ❌ | ❌ | ✅ |
| Gamut | sRGB only | sRGB only | sRGB only | Any (P3, Rec.2020) |
| Alpha transparency | 🟡 #RRGGBBAA | 🟢 rgb(r g b / a) | 🟢 hsl(h s l / a) | 🟢 oklch(L C H / a) |
| Browser support | ✅ 100% | ✅ 100% | ✅ 100% | ✅ 97%+ (2024+) |
HEX: Best for Design Handoff
HEX (hexadecimal) represents each color channel (red, green, blue) as a two-digit base-16 number from 00 to FF (0 to 255 in decimal).
Strengths:
- Universal — every design tool, CSS preprocessor, and browser understands HEX
- Compact — 7 characters for a full color (
#6366f1) - Copy-paste from Figma, Sketch, Adobe XD, and any color picker
- Shorthand —
#fffexpands to#ffffff
Weaknesses:
- Not human-readable —
#6366f1does not intuitively convey “indigo” - Hard to adjust — making a color 10% lighter requires converting to HSL mentally or with a tool
- Alpha is non-obvious —
#6366f180(50% opacity) looks confusing - Case-insensitive —
#6366F1and#6366f1are identical, causing inconsistency in codebases
Use when: You are copying colors from design tools, defining brand colors in a style guide, or working with legacy CSS that uses HEX exclusively.
HEX shorthand rules
| Shorthand | Full | When to use |
|---|---|---|
#fff | #ffffff | Pure white |
#000 | #000000 | Pure black |
#f00 | #ff0000 | Pure red |
#6366f1 | N/A (no shorthand) | Most colors don’t have shorthand |
#6366f180 | 8-digit HEX with alpha | 50% opacity indigo |
RGB: Best for JavaScript Calculations
RGB represents colors as three integer values (0-255) corresponding to the red, green, and blue channels of a display pixel.
Strengths:
- Easy math — interpolate between colors by adjusting 0-255 integer values
- Matches hardware — directly represents how screens emit light (red, green, blue subpixels)
- Clear alpha — modern syntax:
rgb(99 102 241 / 50%) - Canvas/WebGL native — pixel data in
ImageDatais stored as RGBA arrays
Weaknesses:
- Not intuitive —
rgb(99, 102, 241)does not suggest a hue or brightness - Hard to create variations — “make this 20% lighter” requires adjusting all three channel values proportionally
- Non-perceptual — equal mathematical steps produce unequal visual changes
Use when: You need color math in JavaScript, generating dynamic colors programmatically, working with Canvas API or WebGL, or processing pixel data.
// Mixing two colors in RGB (linear interpolation)
function mixColors(c1, c2, ratio) {
return {
r: Math.round(c1.r + (c2.r - c1.r) * ratio),
g: Math.round(c1.g + (c2.g - c1.g) * ratio),
b: Math.round(c1.b + (c2.b - c1.b) * ratio),
};
}
// Generate a random color
function randomColor() {
return `rgb(${Math.random()*255|0}, ${Math.random()*255|0}, ${Math.random()*255|0})`;
}
// Extract color from canvas pixel
const ctx = canvas.getContext('2d');
const [r, g, b, a] = ctx.getImageData(x, y, 1, 1).data;
HSL: Best for Design Systems
HSL represents colors using three intuitive dimensions: Hue (color wheel position, 0-360°), Saturation (color intensity, 0-100%), and Lightness (brightness, 0-100%).
Strengths:
- Human-intuitive —
hsl(239, 84%, 67%)immediately conveys “a vivid color near blue, medium brightness” - Easy variations — change lightness from 67% to 90% for a lighter shade, keeping the same hue and saturation
- Perfect for theming — adjust hue to shift an entire palette; adjust saturation for muted/vibrant variants
- Predictable shade scales — evenly spaced lightness values produce a visually consistent scale
Weaknesses:
- Not perceptually uniform — 50% lightness in yellow looks much brighter than 50% lightness in blue
- Less common in design tool exports — most tools default to HEX
- Saturation behaves oddly at extreme lightness values (0% and 100% lightness are always black and white)
Use when: Building design tokens and CSS custom properties, creating shade scales (50-900), implementing dark mode with lightness inversion, and theming applications.
/* Design tokens with HSL — change --brand-hue to recolor everything */
:root {
--brand-hue: 239;
--brand-sat: 84%;
--brand-50: hsl(var(--brand-hue), var(--brand-sat), 97%);
--brand-100: hsl(var(--brand-hue), var(--brand-sat), 93%);
--brand-200: hsl(var(--brand-hue), var(--brand-sat), 85%);
--brand-300: hsl(var(--brand-hue), var(--brand-sat), 75%);
--brand-400: hsl(var(--brand-hue), var(--brand-sat), 67%);
--brand-500: hsl(var(--brand-hue), var(--brand-sat), 55%);
--brand-600: hsl(var(--brand-hue), var(--brand-sat), 45%);
--brand-700: hsl(var(--brand-hue), var(--brand-sat), 35%);
--brand-800: hsl(var(--brand-hue), var(--brand-sat), 25%);
--brand-900: hsl(var(--brand-hue), var(--brand-sat), 15%);
}
By changing only --brand-hue, you shift the entire palette to a new color family.
OKLCH: The Modern Standard
OKLCH is a perceptually uniform color space introduced in CSS Color Level 4. It represents colors as Lightness (0-1), Chroma (color intensity), and Hue (0-360°) — similar to HSL but with consistent perceptual behavior.
Why OKLCH matters:
- Perceptual uniformity — equal mathematical changes produce equal visual changes. In HSL,
hsl(60, 100%, 50%)(yellow) looks much brighter thanhsl(240, 100%, 50%)(blue) despite having the same lightness value. OKLCH corrects this. - Wide gamut — can represent colors beyond sRGB (Display P3, Rec. 2020)
- Better gradients — avoids the “muddy middle” problem where HSL gradients pass through desaturated gray
/* OKLCH design tokens — perceptually consistent */
:root {
--brand-50: oklch(0.97 0.02 285);
--brand-100: oklch(0.93 0.05 285);
--brand-500: oklch(0.585 0.233 285);
--brand-900: oklch(0.25 0.12 285);
}
/* Smooth gradient without muddy middle */
background: linear-gradient(in oklch, oklch(0.7 0.25 150), oklch(0.7 0.25 270));
Browser support for OKLCH
OKLCH is supported in Chrome 111+, Safari 15.4+, Firefox 113+, and Edge 111+ — covering 97%+ of global users as of 2025. For older browsers, provide an sRGB fallback:
.button {
background: hsl(239, 84%, 67%); /* Fallback for older browsers */
background: oklch(0.585 0.233 285); /* Modern browsers use this */
}
Conversion Formulas
HEX to RGB
function hexToRgb(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
}
RGB to HSL
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}
Accessibility Considerations
Color format choice affects accessibility workflow. When checking WCAG contrast ratios, all formats are mathematically equivalent — the contrast ratio between #6366f1 and #ffffff is the same whether you express the colors in HEX, RGB, or HSL.
However, HSL makes it easier to fix failing contrast ratios:
/* Original: fails WCAG AA on white background */
color: hsl(239, 84%, 67%); /* Contrast ratio: 3.8:1 — FAIL */
/* Fix: just reduce lightness */
color: hsl(239, 84%, 45%); /* Contrast ratio: 5.2:1 — PASS */
With HEX or RGB, fixing contrast requires trial-and-error or a tool. With HSL, you simply reduce the lightness value until the ratio meets 4.5:1.
Check your colors: Use our Color Contrast Checker to verify WCAG AA/AAA compliance.
Recommendation Summary
| Context | Recommended Format | Why |
|---|---|---|
| Quick CSS styling | HEX | Universal, compact, copy-paste from design tools |
| Design system / theme | HSL or OKLCH | Easy variations, hue-based theming |
| JavaScript color manipulation | RGB | Integer math, Canvas/WebGL native |
| Gradients | OKLCH | No muddy midtones, perceptually smooth |
| Future-proof design systems | OKLCH | Perceptual uniformity + wide gamut |
| Accessibility fixes | HSL | Adjust lightness to fix contrast |
For most projects, use HSL for CSS custom properties (design tokens) and HEX for static values. If you are starting a new design system in 2026, consider OKLCH as the primary format — its perceptual uniformity eliminates the inconsistencies that make HSL frustrating for large-scale design systems.
Generate harmonious palettes in any format with our Color Palette Generator.
Frequently Asked Questions
Can I mix color formats in the same CSS file?
Yes. Browsers normalize all color formats to the same internal representation. Using #6366f1 in one rule and hsl(239, 84%, 67%) in another is perfectly valid. However, for consistency and maintainability, most style guides recommend using one primary format (typically HSL for tokens, HEX for one-off values).
Why does HSL yellow look brighter than HSL blue at the same lightness?
Because HSL lightness is not perceptually uniform. hsl(60, 100%, 50%) (yellow) has a much higher relative luminance than hsl(240, 100%, 50%) (blue) despite both having L: 50%. This is because human vision is most sensitive to green light (which yellow contains) and least sensitive to blue. OKLCH solves this by using a perceptually uniform lightness scale.
Should I use rgb() or rgba() for transparency?
In modern CSS (Level 4+), use the space-separated syntax with a slash for alpha: rgb(99 102 241 / 50%). The comma-separated rgba(99, 102, 241, 0.5) still works but is the older syntax. The same applies to hsl() vs hsla() — use hsl(239 84% 67% / 50%).
What is the Display P3 color space and do I need it?
Display P3 is a wide-gamut color space used by Apple devices, modern monitors, and HDR content. It covers ~25% more colors than sRGB. You can access P3 colors using color(display-p3 0.4 0.4 0.95) or OKLCH. For most web applications, sRGB is sufficient. Use P3 when targeting Apple devices or when you need more vibrant greens, reds, and oranges that sRGB cannot represent.
This article is part of our CSS Color Systems Guide series.