HEX: RGB in Base 16
HEX is not a separate color model. It is RGB expressed in hexadecimal notation for compactness. Each channel (R, G, B) becomes two hex digits from 00 to FF, representing the decimal range 0 to 255. The color #FF7F50 is exactly rgb(255, 127, 80). The shorthand #F75 expands each digit by doubling to #FF7755. The 4-digit variant #F75A adds an alpha channel. You can verify this and visually explore any HEX value using the WOWHOW Color Picker, which converts between all formats in real time.
HEX is the web standard for two reasons: it is compact in CSS, and it is what design tools export by default. Every Figma frame, Sketch artboard, and Adobe XD spec sheet outputs HEX. The convention is so entrenched that “hex code” has become a synonym for “web color” in most design conversations. Use HEX for static, opaque design tokens, brand color definitions in config files, and anywhere you are pasting values from a design file with no programmatic manipulation needed. Avoid HEX when you need alpha transparency, when you need to compute shades dynamically, or when the color will be manipulated by JavaScript.
HSL: The Color Model Developers Actually Think In
HSL stands for Hue, Saturation, and Lightness. It was designed specifically to match how humans perceive and describe color, which makes it the most useful model for developers building UI systems. Hue (0–360°) is the color itself, expressed as a position on a color wheel: 0° is red, 120° is green, 240° is blue. Saturation (0–100%) describes how vivid the color is — 100% is fully saturated, 0% is gray. Lightness (0–100%) describes brightness — 50% is the “normal” color, 0% is pure black, 100% is pure white regardless of hue.
/* Coral in HSL */
hsl(16, 100%, 66%)
/* Darker variant — drop lightness */
hsl(16, 100%, 45%)
/* Desaturated, muted version */
hsl(16, 30%, 66%)
/* Dark mode surface — same hue, much darker */
hsl(16, 100%, 12%)
The power of HSL for UI development is that generating shade scales becomes a single-axis operation. Want a 9-step palette from light to dark? Vary only lightness, keep hue and saturation fixed:
function generateShadeScale(hue: number, saturation: number): string[] {
const steps = [95, 85, 75, 65, 55, 45, 35, 25, 15]
return steps.map(l => `hsl(${hue}, ${saturation}%, ${l}%)`)
}
const blueScale = generateShadeScale(220, 80)
// ['hsl(220, 80%, 95%)', 'hsl(220, 80%, 85%)', ..., 'hsl(220, 80%, 15%)']
This is exactly the approach behind design tokens in systems like Radix, Shadcn, and Chakra UI — trivial in HSL, opaque and fragile in HEX or RGB arithmetic. Explore how different hues, saturations, and lightness values interact with the WOWHOW Color System Generator, which builds full palettes from a single base color.
CMYK: The Print Model You Will Rarely Write But Should Understand
CMYK (Cyan, Magenta, Yellow, Key/Black) is a subtractive color model used in print. Unlike screens, which emit light additively, printers deposit ink on a white substrate. Combining all inks moves toward black — though in practice a dedicated black ink (K) is needed for sharp text and deep darks. CSS has no native cmyk() function. CMYK lives in InDesign, Illustrator, Photoshop, and printer RIP software.
As a developer, you encounter CMYK most often when a marketing team sends brand color specs that include CMYK values alongside RGB and HEX, when building print stylesheets with @media print, or when integrating with print-on-demand APIs that accept CMYK product color specs. The practical rule: never use CMYK values directly in CSS. Convert to RGB or HEX at design time. The conversion will not be pixel-perfect to the physical print result — that requires ICC profiles — but it will be close enough for screen display.
The professional pattern is to define your design tokens as HSL variables, use HEX in static config (Tailwind, design files), and reach for rgb() only when doing direct pixel work.
Color Accessibility: WCAG Contrast Ratios
The Web Content Accessibility Guidelines (WCAG) 2.1 specify minimum contrast ratios between text and its background. The two levels developers need to know: AA (minimum) requires 4.5:1 for normal text and 3:1 for large text (18pt or 14pt bold); AAA (enhanced) requires 7:1 for normal text and 4.5:1 for large text. The contrast ratio formula uses relative luminance, calculated from linearized RGB values:
function getRelativeLuminance(r: number, g: number, b: number): number {
const toLinear = (c: number) => {
const n = c / 255
return n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4)
}
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b)
}
function getContrastRatio(
rgb1: [number, number, number],
rgb2: [number, number, number]
): number {
const l1 = getRelativeLuminance(...rgb1)
const l2 = getRelativeLuminance(...rgb2)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
// White text on a blue button
getContrastRatio([255, 255, 255], [37, 99, 235]) // ~4.87 — passes AA
Rather than implementing this manually, use the WOWHOW Color Contrast Checker to test any foreground/background combination against WCAG AA and AAA thresholds instantly. Never use a gray text color lighter than approximately #767676 on a white background (contrast ratio ~4.54:1, just passing AA). For colored text on colored backgrounds, always verify programmatically — visual estimation of contrast is unreliable across hues.
CSS Color Functions: hsl(), rgb(), and color-mix()
Modern CSS Color Level 4 introduced a space-separated syntax and the color-mix() function, both supported in all major browsers as of 2024. The color-mix() function replaces many use cases that previously required JavaScript or Sass:
/* Modern space-separated syntax */
hsl(16 100% 66% / 0.7)
rgb(255 127 80 / 0.7)
/* Blend two colors in any color space */
color-mix(in hsl, hsl(220 80% 50%) 60%, white)
/* Hover state darkening without a separate token */
.button {
--bg: hsl(220 80% 50%);
background: var(--bg);
}
.button:hover {
background: color-mix(in hsl, var(--bg) 85%, black);
}
/* 9-step palette from a single HSL variable */
:root {
--brand-hs: 220 80%;
--brand-50: hsl(var(--brand-hs) 95%);
--brand-500: hsl(var(--brand-hs) 45%);
--brand-900: hsl(var(--brand-hs) 8%);
}
Change --brand-hs once and the entire shade scale updates. This is the reason professional design systems use HSL variables rather than HEX constants. For gradient work, the WOWHOW CSS Gradient Generator lets you compose multi-stop gradients with full color format support, outputting production-ready CSS.
Generating Consistent Color Palettes Programmatically
The algorithm below converts any HEX input to HSL and generates a named shade scale structurally identical to Tailwind’s color system:
function hexToHsl(hex: string): [number, number, number] {
const r = parseInt(hex.slice(1, 3), 16) / 255
const g = parseInt(hex.slice(3, 5), 16) / 255
const b = parseInt(hex.slice(5, 7), 16) / 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)]
}
function generatePalette(hex: string): Record<string, string> {
const [h, s] = hexToHsl(hex)
const steps: Record<string, number> = {
'50': 95, '100': 85, '200': 75, '300': 65, '400': 55,
'500': 45, '600': 35, '700': 25, '800': 15, '900': 8
}
return Object.fromEntries(
Object.entries(steps).map(([name, l]) => [name, `hsl(${h} ${s}% ${l}%)`])
)
}
// generatePalette('#2563EB')
// { '50': 'hsl(220 80% 95%)', ..., '900': 'hsl(220 80% 8%)' }
The Color System Generator does this visually, letting you start from any input color and preview the full palette before exporting to CSS variables or Tailwind config format. For accessible dark mode theming, define a semantic token layer above the palette:
:root {
--color-bg: hsl(0 0% 98%);
--color-text: hsl(0 0% 8%);
--color-accent: hsl(220 80% 45%);
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: hsl(0 0% 8%);
--color-text: hsl(0 0% 98%);
--color-accent: hsl(220 80% 75%);
}
}
With this pattern, dark mode is a three-line change at the token layer. Verify that both light and dark token combinations pass WCAG AA contrast thresholds with the Color Contrast Checker before shipping.
Practical Workflow
Here is the decision flow that covers 95% of color-related development work. Brand color arrives as HEX from design — store it as a CSS variable. Need a shade scale? Convert to HSL, fix hue and saturation, vary lightness. Need a hover or active state? Use color-mix(in hsl, var(--color) 85%, black). Need alpha transparency? Use the slash syntax: hsl(220 80% 45% / 0.8). Need to verify accessibility? Run the foreground/background pair through the contrast checker and target 4.5:1 minimum for body text. Building a canvas or image processing feature? Work in RGB byte arrays, convert at the canvas boundary. Received a CMYK spec? Convert to RGB/HEX before it touches CSS.
Start with the WOWHOW Color Picker any time you need to convert between formats, explore a specific hue, or copy a value in exactly the notation your code needs. It handles HEX, RGB, and HSL conversion in both directions with a clipboard-ready output for each format.
Summary
HEX is RGB in hexadecimal — use it for static tokens and design-file values. RGB is the native screen model — use it for canvas and pixel-level code. HSL maps to human perception — use it for design tokens, shade scales, dark mode, and anything you manipulate programmatically. CMYK is for print — convert to RGB before it touches CSS. color-mix() in modern CSS replaces most preprocessor and JavaScript color arithmetic for dynamic styling.
Every tool mentioned in this guide — the Color Picker, Color Contrast Checker, Color System Generator, and CSS Gradient Generator — is available free at wowhow.cloud. No signup, no rate limits, browser-side processing only. Open the Color Picker now, paste in any brand color, and switch between HEX, RGB, and HSL. Watch how the numbers change while the color stays the same. That is the mental model clicking into place.
Comments · 0
Beta: comments are stored locally on your device and not visible to other readers.
No comments yet. Be the first to share your thoughts.