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.

Color on the web is deceptively complex. A designer sends you a hex code (#3B82F6), your CSS framework uses HSL values, the accessibility checker requires contrast ratios computed from relative luminance, and your design system needs to generate lighter and darker shades programmatically. Each of these tasks uses a different color model — and converting between them incorrectly produces subtly wrong colors that are hard to diagnose visually.

HEX: The Web's Default Color Notation

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 Developer-Friendly Color Model

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%);

Tailwind CSS and most modern design systems define their color palettes using HSL internally, because generating consistent shade scales (50-950) from a single base color requires only adjusting the lightness value.

Converting Between Color Models

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 }

Color and Accessibility: WCAG Contrast

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.

Modern CSS Color Features

  • 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