CSS Color Formats Guide

HEX, RGB, HSL — how they work and when to use each

ConvertersFebruary 20, 202610 min readBy Keyur Patel

I used to just copy hex codes from Figma without thinking. Then I had to build a dark mode system and suddenly needed to understand why HSL exists. If you've ever wondered why there are so many ways to write the same color, you're asking the right question — and the answer changed how I write CSS entirely.

After fifteen years of frontend work, I've landed on strong opinions about which format to reach for and when. This isn't a history lesson. It's the practical guide I wish someone had handed me before I spent a weekend manually converting 200 hex values into a theme system.

How Browsers Render Colors

Computer screens generate colors using additive color mixing with red, green, and blue light. By combining different intensities of these three channels, displays can render millions of unique colors.

CSS color formats are simply different ways to describe those same underlying RGB values. Whether you use HEX, RGB, or HSL, browsers ultimately convert everything into red, green, and blue channel data before rendering pixels to the screen.

Important:

Different color formats are functionally equivalent in the browser. The main difference is readability, developer experience, and how easy the format is to manipulate programmatically.

HEX Colors

HEX (hexadecimal) colors represent red, green, and blue channels as two-digit hexadecimal values ranging from 00 to FF (0–255 in decimal). The format starts with a hash symbol followed by six hexadecimal characters.

#FF5733→ R:255 G:87 B:51
#6366f1→ R:99 G:102 B:241
#fff→ Shorthand for #ffffff

HEX values are compact and widely used in design systems, UI libraries, and CSS frameworks. Designers frequently export colors from tools like Figma, Sketch, or Adobe XD in HEX format, making it convenient for frontend developers.

HEX also supports alpha transparency using 8-digit notation: #FF573380 where the final two digits define opacity.

Best for: Design tokens, brand colors, utility classes, and copying values directly from design tools.

RGB and RGBA

RGB defines colors using three numeric values representing red, green, and blue intensity levels between 0 and 255. RGBA extends RGB with an alpha channel for transparency.

rgb(255, 87, 51)→ Solid orange-red
rgba(255, 87, 51, 0.5)→ 50% transparent
rgba(0, 0, 0, 0.1)→ Subtle dark overlay

RGB is particularly useful when generating colors dynamically in JavaScript because channel values can be manipulated mathematically. Many canvas APIs, image-processing libraries, and animation systems also use RGB internally.

Transparency support makes RGBA ideal for overlays, modal backdrops, shadows, glassmorphism effects, and layered UI components.

Best for: Transparency effects, overlays, canvas work, and dynamic color calculations.

HSL and HSLA

HSL stands for Hue, Saturation, and Lightness — a color model designed to match how humans naturally think about colors.

  • Hue (0–360°): Position on the color wheel.
  • Saturation (0–100%): Color intensity or vibrancy.
  • Lightness (0–100%): Brightness of the color.
hsl(9, 100%, 60%)→ Bright orange-red
hsl(9, 100%, 40%)→ Darker version
hsl(9, 30%, 60%)→ Muted version

HSL makes it very easy to create consistent UI variations. You can keep the same hue while adjusting saturation and lightness for hover states, dark mode, muted colors, and accessibility improvements.

Modern design systems often prefer HSL because it simplifies theme generation and color scaling compared to manually editing RGB or HEX values.

Best for: Theming, color systems, hover states, dark mode, and palette generation.

Named CSS Colors

CSS also supports predefined color keywords such as red, blue, tomato, and rebeccapurple.

While named colors are easy to read, they provide less precision and consistency than HEX or HSL. Most production applications use explicit color values instead of keywords.

tomato
royalblue
gold
slategray
rebeccapurple

Quick Comparison

FormatTransparencyHuman-readableBest use
HEX✅ (8-digit)MediumDesign tokens, brand colors
RGBLowExact channel control
RGBALowTransparent overlays
HSLHighTheming and palettes
HSLAHighTransparent themed colors

Best Practices for CSS Colors

  • Use CSS variables: Store reusable colors in variables for consistency.
  • Maintain accessibility: Ensure sufficient contrast between text and backgrounds.
  • Prefer HSL for themes: It simplifies generating lighter and darker variants.
  • Avoid hardcoding too many values: Centralize colors in a design system.
  • Use transparency carefully: Excessive opacity layers can reduce readability.

Convert Colors Online

🎨 Color Converter

Convert between HEX, RGB, HSL, and RGBA color formats instantly.

Choosing the Right Color Format for Your Project

With multiple color formats available, choosing the right one depends on your specific use case. Here is a practical decision framework based on real-world scenarios that frontend developers encounter daily.

HEX for Static Design Tokens

Use HEX values for brand colors, design system tokens, and any color that does not need programmatic manipulation. HEX is the lingua franca between design tools (Figma, Sketch) and code. Designers export in HEX, component libraries document in HEX, and it is the most compact format for static values. Store your brand palette as HEX in CSS custom properties or a design tokens file.

RGB for JavaScript Color Manipulation

When you need to manipulate colors programmatically — blending, interpolating, adjusting brightness in animations, or working with Canvas/WebGL — RGB is the natural choice. Channel values map directly to mathematical operations: adding, averaging, and lerping between colors is straightforward with 0–255 integer values. Libraries like Three.js, D3, and canvas APIs all work in RGB space internally.

HSL for Dynamic Theming

HSL is ideal for generating color variations at scale. By keeping the hue constant and adjusting saturation and lightness, you can produce hover states, disabled states, focus rings, and dark mode variants from a single base color. This makes HSL perfect for design systems that need 10+ shades of each brand color without manually picking each one.

/* HSL theming with CSS custom properties */
:root {
  --primary-h: 239;
  --primary-s: 84%;
  --primary-l: 67%;
  --primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l));
  --primary-hover: hsl(var(--primary-h), var(--primary-s), 57%);
  --primary-light: hsl(var(--primary-h), var(--primary-s), 92%);
  --primary-dark: hsl(var(--primary-h), var(--primary-s), 45%);
}

/* Dark mode — same hue, adjusted lightness */
[data-theme="dark"] {
  --primary-l: 72%;
  --primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l));
}

CSS Custom Properties with HSL

The most powerful pattern for modern theming combines CSS custom properties with HSL decomposition. By storing hue, saturation, and lightness as separate variables, you can create an entire palette from minimal configuration and switch themes by changing just a few values. This approach eliminates color duplication and makes dark mode trivial to implement.

Accessibility and Contrast Calculations

When calculating WCAG contrast ratios for accessibility compliance, you need to work in linear RGB space (not sRGB). The relative luminance formula requires converting sRGB channel values to linear light values before computing contrast. Libraries likecolor.js and chroma-js handle this correctly. Never calculate contrast directly from HSL lightness — it does not correspond to perceptual brightness and will give incorrect WCAG results.

// WCAG 2.1 contrast ratio calculation
function luminance(r, g, b) {
  const [rs, gs, bs] = [r, g, b].map(c => {
    c = c / 255
    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
  })
  return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}

function contrastRatio(rgb1, rgb2) {
  const l1 = luminance(...rgb1)
  const l2 = luminance(...rgb2)
  const lighter = Math.max(l1, l2)
  const darker = Math.min(l1, l2)
  return (lighter + 0.05) / (darker + 0.05)
}

// WCAG AA requires 4.5:1 for normal text, 3:1 for large text
contrastRatio([255, 255, 255], [99, 102, 241])  // → 4.73:1 ✅

Color Functions in Modern CSS

CSS Color Level 4 introduces powerful new color functions that reduce the need for preprocessors and JavaScript color manipulation. These features are landing in browsers now and represent the future of CSS color management.

color-mix()

The color-mix() function blends two colors in a specified color space. This is supported in all modern browsers and eliminates the need for Sass mix functions or JavaScript color blending. You can create hover states, mid-tones, and overlay colors purely in CSS.

/* Mix two colors in sRGB space */
.button:hover {
  background: color-mix(in srgb, var(--primary) 80%, black);
}

/* Create a semi-transparent version */
.overlay {
  background: color-mix(in srgb, var(--primary) 20%, transparent);
}

/* Mix in oklch for perceptually uniform results */
.gradient-mid {
  color: color-mix(in oklch, #ff0000 50%, #0000ff);
}

Relative Color Syntax

Relative color syntax allows you to derive new colors from existing ones by modifying individual channels. This is incredibly powerful for creating color scales, adjusting lightness for dark mode, or desaturating colors — all without leaving CSS. It uses thefrom keyword inside color functions.

/* Lighten a color by adjusting the L channel in HSL */
.card-bg {
  background: hsl(from var(--primary) h s calc(l + 30%));
}

/* Desaturate for a muted variant */
.muted {
  color: hsl(from var(--primary) h calc(s - 40%) l);
}

/* Add transparency to any color */
.glass {
  background: rgb(from var(--primary) r g b / 0.15);
}

oklch() — Perceptually Uniform Colors

OKLCH is a perceptually uniform color space where equal numeric changes produce equal visual changes. Unlike HSL where hsl(60, 100%, 50%) (yellow) looks much brighter than hsl(240, 100%, 50%) (blue) despite having the same lightness value, OKLCH ensures consistent perceived lightness across all hues. This makes it ideal for generating accessible color palettes where all colors have equal visual weight.

/* oklch(lightness chroma hue) */
.primary { color: oklch(0.6 0.2 260); }     /* Indigo */
.success { color: oklch(0.6 0.2 145); }     /* Green — same perceived brightness */
.warning { color: oklch(0.6 0.2 85); }      /* Orange — same perceived brightness */

/* Create a full palette with consistent lightness */
:root {
  --brand-50:  oklch(0.97 0.02 260);
  --brand-100: oklch(0.93 0.05 260);
  --brand-500: oklch(0.60 0.20 260);
  --brand-900: oklch(0.30 0.10 260);
}

The Evolving CSS Color Level 4 Spec

CSS Color Level 4 also introduces the color() function for accessing wide-gamut color spaces like Display P3 and Rec. 2020, which can represent colors that sRGB cannot. Modern displays (especially Apple devices) support P3, meaning you can use more vibrant reds, greens, and blues than HEX or RGB allow. Use the@supports rule to progressively enhance colors for capable displays while providing sRGB fallbacks.

/* Wide-gamut color with sRGB fallback */
.vibrant-button {
  background: #6366f1;  /* sRGB fallback */
  background: color(display-p3 0.35 0.38 0.96);  /* Wider gamut */
}

/* Feature detection for wide-gamut support */
@supports (color: color(display-p3 1 1 1)) {
  :root {
    --primary: color(display-p3 0.35 0.38 0.96);
  }
}

Browser support for CSS Color Level 4 features is rapidly improving. As of 2024,color-mix() and oklch() are supported in all major browsers. Relative color syntax has shipped in Chrome and Safari with Firefox support in progress. Start using these features with fallbacks today — they simplify color management significantly and reduce reliance on preprocessors.

Summary

Here's my rule of thumb after building multiple design systems: HEX for your design tokens and anything Figma hands you. HSL for themes, dark mode, and generating shade scales programmatically. RGB when you're doing math on colors in JavaScript — canvas work, animations, blending.

Pick based on what you're doing with the color, not what you're used to typing. Most teams end up using all three in a single project, and that's fine. Just be consistent within each context.