← Back to Blog

Color Theory for Developers: HEX/RGB/HSL, Accessibility, Contrast Ratios & Palette Generation

Why Should Developers Understand Color Theory?

Color isn't just the designer's domain. As a developer, understanding color models, contrast, and accessibility standards helps you:

  • Accurately implement designs without color distortion
  • Build WCAG-compliant accessible interfaces
  • Write dynamic theming and color-switching logic
  • Make sound color decisions when no designer is available

Color Models Explained

HEX (Hexadecimal)

The most common color notation on the web, formatted as #RRGGBB, where each pair is a hex value (00-FF) for a channel.

#FF0000  -> Pure red
#00FF00  -> Pure green
#0000FF  -> Pure blue

8-digit HEX includes an alpha channel: #RRGGBBAA, e.g., #FF000080 is semi-transparent red.

RGB / RGBA

An additive model using red, green, and blue channels (0-255).

color: rgb(255, 0, 0);
color: rgba(255, 0, 0, 0.5);

Modern CSS also supports space syntax: rgb(255 0 0 / 50%).

HSL / HSLA

HSL aligns with human intuition about color, with three components:

  • H (Hue): 0-360 degrees on the color wheel
  • S (Saturation): 0-100%, color intensity
  • L (Lightness): 0-100%, 0 is black, 100 is white
color: hsl(0, 100%, 50%);    /* Pure red */
color: hsl(120, 100%, 50%);  /* Pure green */
color: hsl(240, 100%, 50%);  /* Pure blue */

Model Conversion

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);
  const min = Math.min(r, g, b);
  let h, s, l = (max + min) / 2;

  if (max === min) {
    h = s = 0;
  } 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); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }
    h /= 6;
  }
  return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}

Accessibility and Contrast

WCAG Contrast Standards

The Web Content Accessibility Guidelines define minimum contrast ratios:

Level Normal Text Large Text (18pt+ or 14pt bold)
AA (minimum) 4.5:1 3:1
AAA (enhanced) 7:1 4.5:1

Contrast Calculation

Contrast is based on relative luminance:

function getLuminance(r, g, b) {
  const [rs, gs, bs] = [r, g, b].map(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 getContrastRatio(rgb1, rgb2) {
  const l1 = getLuminance(...rgb1);
  const l2 = getLuminance(...rgb2);
  const [lighter, darker] = l1 > l2 ? [l1, l2] : [l2, l1];
  return (lighter + 0.05) / (darker + 0.05);
}

Common Contrast Issues

  • Light gray text on white often fails (e.g., #999 on #FFF is only 2.85:1)
  • Brand colors used as button text need contrast verification
  • Dark mode also requires contrast checks

Palette Generation

Classic Color Schemes

Complementary

Two colors opposite on the color wheel (180° apart), strong contrast.

Analogous

Colors adjacent on the wheel (30°-60° apart), harmonious.

Triadic

Three colors equally spaced (120° apart), vibrant and balanced.

Monochromatic

Same hue with varying saturation and lightness for layered depth.

function generateMonochromatic(baseHue) {
  return [
    `hsl(${baseHue}, 80%, 90%)`,
    `hsl(${baseHue}, 70%, 70%)`,
    `hsl(${baseHue}, 80%, 50%)`,
    `hsl(${baseHue}, 70%, 30%)`,
    `hsl(${baseHue}, 60%, 15%)`
  ];
}

Programmatic Palette Generation with HSL

HSL is the best model for programmatic palette generation — just adjust the hue angle:

function generatePalette(baseHue, scheme = 'analogous') {
  const schemes = {
    complementary: [0, 180],
    analogous: [-30, 0, 30],
    triadic: [0, 120, 240],
    tetradic: [0, 90, 180, 270]
  };
  const offsets = schemes[scheme] || schemes.analogous;
  return offsets.map(offset =>
    `hsl(${(baseHue + offset + 360) % 360}, 65%, 55%)`
  );
}

Design Token Practice

Modern design systems use CSS variables to define color scales:

:root {
  --primary-50:  #eff6ff;
  --primary-100: #dbeafe;
  --primary-500: #3b82f6;
  --primary-700: #1d4ed8;
  --primary-900: #1e3a8a;
}

Advanced CSS Color Functions

color-mix()

.result { background: color-mix(in srgb, red 50%, blue 50%); }

color() for Different Color Spaces

.vivid { color: color(display-p3 1 0 0); }
.darker { color: hsl(from var(--base) h s calc(l - 10%)); }

prefers-color-scheme for Dark Mode

:root { --bg: #ffffff; --text: #1a1a1a; }

@media (prefers-color-scheme: dark) {
  :root { --bg: #1a1a1a; --text: #ffffff; }
}

body { background: var(--bg); color: var(--text); }

Tools and Resources

Tool Purpose
Coolors Palette generation
Adobe Color Color wheel and rules
WebAIM Contrast Checker Contrast checking
OKLCH Picker OKLCH color space picker
← Back to Blog