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)];
}Practice the concepts from this article using ToolZeno's browser-based developer tools. No sign-up, no server tracking — 100% client-side.
HEX ↔ RGB Converter
FreeConvert color codes instantly between HEX, HEX with Alpha, RGB, RGBA, HSL, and CMYK formats. Features live visual previews, color channel breakdown, copyable CSS snippets, and history. 100% free & client-side.
RGB ↔ HSL Converter
FreeConvert colors instantly between RGB, RGBA, HSL, and HSLA formats online. Features live visual previews, interactive channel sliders, copyable CSS declarations, and history. 100% free & client-side.
RGB ↔ CMYK Converter
FreeConvert colours instantly between RGB, RGBA, and CMYK formats online. Features live visual previews, print ink channel sliders, copyable CSS declarations, and print guidance. 100% free & client-side.
Color Palette Generator
FreeCreate beautiful color palettes using color theory, extract colors from images locally, lock colors, preview live UI mockups, and export to CSS, SCSS, Tailwind, JSON, SVG, PNG, and PDF 100% client-side.
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.