DailyTools
All articles
Web DevelopmentApril 8, 20267 min read

Color Models for Web Developers: HEX, RGB, HSL, and When to Use Each

Designers hand you HEX codes, CSS needs RGB or HSL, and accessibility tools require specific contrast ratios. Understand how color models work and convert between them confidently.

DT
DailyTools Editorial · About

A designer sends you #3B82F6. Your CSS uses hsl(217, 91%, 60%). Your accessibility checker wants the contrast ratio against white. Your design system needs to generate a full shade scale from that base color. These are the same color expressed four different ways, and knowing when to use which representation is what separates developers who fight CSS from developers who understand it.

HEX: compact but unreadable

Hexadecimal color codes represent red, green, and blue channels as pairs of hex digits: #RRGGBB. Each channel ranges from 00 (0) to FF (255). The shorthand form #RGB expands each digit: #F0A becomes #FF00AA. An optional fourth pair (#RRGGBBAA) adds an alpha channel for transparency.

HEX is compact and universal — every design tool, browser, and CSS processor supports it. But it is opaque to human intuition: looking at #3B82F6, few developers can visualize that this is a medium-bright blue. For programmatic color manipulation (generating palettes, adjusting brightness, calculating contrast), HEX is cumbersome because the perceptual qualities of a color are not encoded in its representation.

RGB: How Screens Actually Work

RGB (Red, Green, Blue) is the native color model of digital displays. Every pixel on your screen is a cluster of three sub-pixels — red, green, and blue — that combine additively to produce the visible color. rgb(0, 0, 0) is black (no light), rgb(255, 255, 255) is white (full light), and rgb(255, 0, 0) is pure red.

In CSS, RGB values are expressed as rgb(R, G, B) or rgba(R, G, B, A) where A is the alpha transparency from 0 (fully transparent) to 1 (fully opaque). Modern CSS also accepts the space-separated syntax: rgb(59 130 246 / 0.5) for a semi-transparent blue.

css
/* RGB in CSS */
.element {
  color: rgb(59, 130, 246);           /* Opaque blue */
  background: rgba(59, 130, 246, 0.1); /* 10% opacity blue */

  /* Modern syntax (CSS Colors Level 4) */
  border-color: rgb(59 130 246 / 0.5); /* 50% opacity */
}

HSL: the one you should use for design tokens

HSL (Hue, Saturation, Lightness) represents color in terms that match human perception. Hue is the color wheel position (0-360 degrees: 0 = red, 120 = green, 240 = blue). Saturation is the color intensity (0% = gray, 100% = vivid). Lightness is the brightness (0% = black, 50% = pure color, 100% = white).

HSL's killer feature is that generating color variations is trivial: to make a lighter shade, increase lightness. To make a muted version, decrease saturation. To create a complementary color, add 180 to the hue. These operations are intuitive in HSL but require complex math in RGB or HEX.

css
/* HSL makes color variations trivial */
:root {
  --brand-hue: 217;
  --brand-sat: 91%;

  --brand-50:  hsl(var(--brand-hue), var(--brand-sat), 97%);  /* Lightest */
  --brand-100: hsl(var(--brand-hue), var(--brand-sat), 93%);
  --brand-200: hsl(var(--brand-hue), var(--brand-sat), 86%);
  --brand-500: hsl(var(--brand-hue), var(--brand-sat), 60%);  /* Primary */
  --brand-700: hsl(var(--brand-hue), var(--brand-sat), 40%);
  --brand-900: hsl(var(--brand-hue), var(--brand-sat), 20%);  /* Darkest */
}

/* Complementary color: rotate hue by 180° */
--complement: hsl(calc(var(--brand-hue) + 180), var(--brand-sat), 60%);

This is why Tailwind CSS and most modern design systems define palettes using HSL internally — generating a consistent shade scale (50 through 950) from a single base color requires only adjusting the lightness value. If you're building a design system from scratch, define your colors as HSL variables and derive everything else from them.

The math behind the conversions

HEX and RGB are different representations of the same information — converting between them is simple arithmetic. HEX #3B82F6 means R=0x3B (59), G=0x82 (130), B=0xF6 (246), so rgb(59, 130, 246). Converting to HSL requires a more involved algorithm that extracts the hue angle, saturation ratio, and lightness from the RGB values.

javascript
// HEX → 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 → 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);
  const l = (max + min) / 2;
  if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) };

  const d = max - min;
  const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  let h = 0;
  if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
  else if (max === g) h = ((b - r) / d + 2) / 6;
  else h = ((r - g) / d + 4) / 6;

  return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}

hexToRgb('#3B82F6');    // { r: 59, g: 130, b: 246 }
rgbToHsl(59, 130, 246); // { h: 217, s: 91, l: 60 }

The accessibility trap: HSL lightness isn't perceptual luminance

The Web Content Accessibility Guidelines (WCAG) require minimum contrast ratios between text and background colors: 4.5:1 for normal text and 3:1 for large text (Level AA). Contrast is calculated from the relative luminance of the two colors — a formula based on linearized RGB values, not raw RGB or HSL lightness.

This distinction matters: HSL lightness is not the same as perceptual luminance. Green (hsl(120, 100%, 50%)) and blue (hsl(240, 100%, 50%)) have the same HSL lightness (50%) but very different perceived brightness — green appears much brighter to the human eye. For accessibility calculations, always use the WCAG relative luminance formula, not HSL lightness.

What's new in CSS color (and what to use now)

  • oklch(): A perceptually uniform color space now supported in all major browsers. Lightness in OKLCH actually correlates with perceived brightness, making it superior to HSL for generating consistent color palettes.
  • color-mix(): Mix two colors in any color space. color-mix(in srgb, #3B82F6, white 20%) creates a 20% lighter version of a color without manual math.
  • Relative colors: CSS now supports relative color syntax like hsl(from var(--base) h s calc(l + 20%)) to derive new colors from existing ones.
  • Wide-gamut colors: Display P3 and Rec. 2020 color spaces offer colors beyond sRGB — visible on modern HDR displays. Use @media (color-gamut: p3) to detect support.

Try the free tool referenced in this article

Color Converter