Back to Blog Index
Design & Accessibility12 min read

CSS Color Systems Deep-Dive: HEX, RGB, HSL, CMYK, and Modern Color Spaces

Master CSS color representation from HEX and RGB to HSL and the new oklch() color space. Understand color conversion mathematics, gamut mapping, and building accessible colour palettes.

ToolZeno Editorial Team

Software Architecture & Security Guild

Published 2026-06-28 (Updated 2026-07-30)
#CSS#Color#HEX#RGB#HSL#Design Systems#Frontend

Understanding Color Models: Additive vs Subtractive

Color in digital design is fundamentally governed by two models: additive colour mixing (used by screens) and subtractive colour mixing (used by printers). Understanding this distinction is critical for any developer or designer working with color across digital and print media.

Additive colour mixing (RGB) starts with black (no light) and adds red, green, and blue light to create colours. When all three channels are at maximum intensity, the result is white. Screens — monitors, phones, tablets, TVs — use additive mixing because they emit light. The RGB colour model directly maps to the physical hardware: each pixel on a screen contains tiny red, green, and blue sub-pixels that glow at varying intensities.

Subtractive colour mixing (CMYK) starts with white (the paper reflecting all light) and adds cyan, magenta, yellow, and key (black) inks that absorb (subtract) specific wavelengths. When all inks are applied at maximum density, the result is theoretically black (though in practice it's a dark brown, which is why a dedicated black Key ink is used). Professional printing uses CMYK because ink physically absorbs light from a reflective surface.

The fundamental implication for web developers is that colours designed in RGB on screen may look different when printed in CMYK. Some vibrant screen colours (particularly bright blues, saturated greens, and neon pinks) fall outside the CMYK colour gamut and cannot be accurately reproduced in print. Tools that convert between RGB and CMYK help designers understand these limitations before sending files to print.

HEX, RGB, and HSL: The Three Pillars of Web Colour

Web development uses three primary colour notations, each offering different advantages for different workflows.

HEX (#RRGGBB) is a hexadecimal representation of RGB values. Each pair of hex digits represents one channel: #FF0000 is red (R=255, G=0, B=0), #00FF00 is green, #0000FF is blue, and #808080 is medium grey. HEX is compact and widely used in CSS, design specifications, and brand guidelines. The shorthand form (#RGB) duplicates each digit: #F00 equals #FF0000. An eight-digit form (#RRGGBBAA) adds alpha transparency.

RGB (rgb(R, G, B)) specifies each channel as a number from 0 to 255 (or as a percentage). This notation maps directly to the physical hardware model and is intuitive for understanding colour mixing. Adding a fourth parameter creates rgba(R, G, B, A) with alpha transparency from 0 (fully transparent) to 1 (fully opaque).

HSL (hsl(H, S%, L%)) describes colour using human-perceptual terms: Hue (the colour angle on a 0-360° wheel: 0° = red, 120° = green, 240° = blue), Saturation (0% = grey, 100% = fully vivid), and Lightness (0% = black, 50% = pure colour, 100% = white). HSL is dramatically more intuitive for creating colour variations: to make a lighter tint, increase lightness; to desaturate, reduce saturation; to create a complementary colour, add 180° to the hue. This makes HSL the preferred model for programmatic colour palette generation.

Production TypeScript colour conversion functions.

// Convert HEX to RGB
function hexToRgb(hex: string): [number, number, number] {
  const cleaned = hex.replace('#', '');
  return [
    parseInt(cleaned.substring(0, 2), 16),
    parseInt(cleaned.substring(2, 4), 16),
    parseInt(cleaned.substring(4, 6), 16),
  ];
}

// Convert RGB to HSL
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
  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 [0, 0, 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 [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
}

Modern CSS Color Spaces: oklch(), lab(), and Display P3

CSS Color Level 4 introduces new colour functions that address fundamental limitations of RGB and HSL. These modern colour spaces provide perceptually uniform colour manipulation and access to wider colour gamuts.

oklch() (Oklch Lightness, Chroma, Hue) is the most significant advancement. Unlike HSL where changing the hue at constant lightness produces colours that appear to have different brightness levels (yellow at 50% lightness appears much brighter than blue at 50% lightness), oklch's lightness channel is perceptually uniform — colours with the same L value actually appear equally bright to human vision. This makes oklch ideal for generating accessible colour palettes where different hues need consistent perceived brightness.

lab() and lch() (CIE L*a*b* and CIE L*C*h°) are device-independent colour spaces based on human vision research. L represents perceptual lightness (0 = black, 100 = white), a* represents the green-red axis, and b* represents the blue-yellow axis. lch() is the polar coordinate version using lightness, chroma (colour intensity), and hue angle. These spaces encompass all colours visible to the human eye, far exceeding the sRGB gamut.

Display P3 is a wide colour gamut supported by modern Apple devices, HDR monitors, and flagship Android phones. It provides approximately 25% more colours than sRGB, particularly in saturated reds, greens, and oranges. CSS supports Display P3 colours via the color() function: color(display-p3 1 0.5 0). This allows web designers to use more vibrant colours on capable devices while falling back to sRGB on standard monitors.

  • Use oklch() for programmatic palette generation — its perceptual uniformity produces consistently readable colour sets.
  • Use HSL for quick, intuitive colour adjustments in design workflows.
  • Use HEX for compact colour references in brand guidelines and CSS variables.
  • Use the @media (color-gamut: p3) query to serve wider-gamut colours to capable displays.
  • Always define fallback sRGB colours before Display P3 colours for browser compatibility.

Programmatic Colour Palette Generation for Design Systems

Design systems require systematic approaches to colour that produce consistent, accessible, and aesthetically pleasing palettes. Rather than hand-picking individual colours, modern design systems generate colour scales programmatically using colour space mathematics.

The most common approach generates a scale of 10-12 shades from a single base colour. In HSL, this is achieved by holding the hue constant and varying lightness from very light (95%) to very dark (10%) while slightly adjusting saturation (higher saturation at mid-lightness, lower at extremes). Tailwind CSS, Radix Colours, and Open Props all use this approach to generate their colour scales.

Complementary and analogous colour relationships are calculated using hue rotation. Complementary colours (maximum contrast) sit 180° apart on the hue wheel. Analogous colours (harmonious, low contrast) sit 30-60° apart. Triadic colours (balanced contrast) sit 120° apart. Split-complementary colours (vivid but balanced) combine a base with the two colours adjacent to its complement.

For accessibility, every colour in a palette must be validated against contrast requirements. Light shades (used as backgrounds) need dark text companions with 4.5:1 contrast ratios (WCAG AA). Dark shades (used as text or interactive elements) need sufficient contrast against both light and dark mode backgrounds. ToolZeno's Color Palette Generator and Color Contrast Checker automate this validation entirely in your browser, allowing rapid iteration without manual calculations.

Frequently Asked Questions

Q:What is the difference between HEX, RGB, and HSL colour values?

HEX (#FF5733) is a compact hexadecimal notation. RGB (rgb(255, 87, 51)) specifies red, green, blue channel intensities. HSL (hsl(11, 100%, 60%)) uses human-intuitive hue, saturation, lightness values. All three represent the same sRGB colours — they're just different notations.

Q:Why do some screen colours look different when printed?

Screens use additive RGB colour mixing (light emission), while printers use subtractive CMYK (ink absorption). Some vibrant RGB colours — particularly bright blues and neon greens — fall outside the CMYK gamut and must be approximated, causing visible differences.

Q:What is oklch() and why should I use it?

oklch() is a perceptually uniform colour space where equal lightness values actually appear equally bright to human vision (unlike HSL). This makes it ideal for generating accessible colour palettes where different hues need consistent perceived brightness.

Q:How do I create a colour palette that works in both light and dark mode?

Generate a 10-shade scale from each base hue using HSL lightness variation. Use light shades (50-200 range) as backgrounds in light mode and dark shades (700-950) in dark mode. Verify every background-text combination meets WCAG 4.5:1 contrast requirements.

Explore More Technical Guides

Design & Accessibility

Understanding Web Accessibility & WCAG 2.1 Contrast Ratios for Modern Web Apps

A comprehensive developer's masterclass on WCAG 2.1 AA/AAA color contrast standards, relative luminance math, color blindness vision deficiencies, and building accessible UI design systems.

12 min readRead
Security & Cryptography

JSON Web Tokens (JWT) Deep-Dive: Architecture, Security Standards, and Local Debugging

An in-depth security engineering guide to RFC 7519 JWT structure, digital signatures (HS256 vs RS256), token vulnerability vectors, and safe client-side debugging.

14 min readRead
Performance & Optimization

Image Optimization Strategies for Core Web Vitals: WebP, SVG, and Compression Techniques

Master image compression algorithms, next-generation WebP/AVIF file formats, SVG vector minification, and responsive image loading to achieve 100 Lighthouse performance scores.

12 min readRead
Security & Cryptography

Understanding Password Entropy, Key Derivation, and Cryptographic Hash Functions

Explore the mathematics of password entropy, NIST 800-63B security standards, salt iterations, and SHA-256 vs Argon2 key derivation functions for modern authentication systems.

13 min readRead
Developer Workflows

The Developer's Guide to Cron Expressions, Syntax Parsing, and Execution Schedules

Learn standard 5-field cron syntax, special wildcards, Quartz scheduler differences, common scheduling patterns, and how to validate background cron jobs accurately.

11 min readRead
Developer Workflows

JSON Formatting & Validation in High-Performance Web Applications

Discover best practices for parsing multi-megabyte JSON payloads, handling circular object references, schema validation, Web Worker parsing, and safe browser-based formatting.

12 min readRead
Developer Workflows

Base64 Encoding & Decoding: How It Works, When to Use It, and Common Pitfalls

A thorough technical guide to the Base64 encoding algorithm, its mathematical foundations, practical use cases in web development, and critical security misunderstandings.

11 min readRead
Developer Workflows

Regular Expressions Mastery: Pattern Syntax, Performance Traps, and Real-World Recipes

Master regex pattern syntax from fundamentals to advanced lookaheads, understand catastrophic backtracking, and learn production-tested patterns for email, URL, and data validation.

14 min readRead