csscolorsdesign
CSS Color Tools: Conversion, Contrast, and Palette Generation
· Cosyslabs
CSS now supports eight color formats: HEX, RGB, HSL, HWB, LAB, LCH, OKLCH, and OKLAB. Each serves a different use case — HEX for brevity, HSL for programmatic manipulation, OKLCH for perceptually uniform palettes. Understanding when to use each format, how to check contrast for accessibility, and how to build systematic color palettes separates good UI from great UI.
CSS Color Formats
HEX
color: #FF5733; /* 6-digit: #RRGGBB */
color: #F53; /* 3-digit shorthand: #RGB → #RRGGBB */
color: #FF573380; /* 8-digit with alpha: #RRGGBBAA */
color: #F538; /* 4-digit with alpha */
RGB
color: rgb(255, 87, 51); /* Legacy comma syntax */
color: rgb(255 87 51); /* Modern space syntax */
color: rgb(255 87 51 / 0.5); /* With alpha */
color: rgba(255, 87, 51, 0.5); /* Legacy alpha syntax */
color: rgb(100% 34.1% 20%); /* Percentage values */
HSL
color: hsl(11, 100%, 60%); /* Legacy */
color: hsl(11deg 100% 60%); /* Modern — explicit degrees */
color: hsl(0.031turn 100% 60%); /* Turn units */
color: hsl(11 100% 60% / 0.5); /* With alpha */
OKLCH (Modern — Recommended)
color: oklch(0.65 0.2 30); /* L C H */
color: oklch(65% 0.2 30deg); /* Percentage lightness */
color: oklch(0.65 0.2 30 / 0.5); /* With alpha */
OKLCH is perceptually uniform — the same L value appears equally bright regardless of hue. This makes it ideal for design tokens.
Other Modern Formats
/* HWB — Hue, Whiteness, Blackness (intuitive mixing) */
color: hwb(11 0% 0%);
/* LAB — perceptually uniform (older alternative to OKLCH) */
color: lab(55% 45 35);
/* LCH — Lightness, Chroma, Hue in LAB */
color: lch(55% 60 30);
/* OKLAB — similar to LAB but better perceptual uniformity */
color: oklab(0.65 0.15 0.1);
/* Display P3 — wider gamut than sRGB */
color: color(display-p3 0.9 0.2 0.1);
Color Space Conversion
// HEX to RGB
function hexToRgb(hex) {
const clean = hex.replace("#", "");
const full = clean.length === 3
? clean.split("").map(c => c + c).join("")
: clean;
return {
r: parseInt(full.slice(0, 2), 16),
g: parseInt(full.slice(2, 4), 16),
b: parseInt(full.slice(4, 6), 16),
};
}
// RGB to HEX
function rgbToHex(r, g, b) {
return "#" + [r, g, b]
.map(v => Math.round(Math.clamp(v, 0, 255)).toString(16).padStart(2, "0"))
.join("")
.toUpperCase();
}
// RGB to 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);
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;
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100),
};
}
WCAG Contrast Checking
WCAG 2.1 requires minimum contrast ratios for text readability:
- AA normal text: 4.5:1
- AA large text (≥ 18pt / 14pt bold): 3:1
- AAA normal text: 7:1
- UI components: 3:1
function relativeLuminance(r, g, b) {
const toLinear = c => {
const v = c / 255;
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
};
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
}
function contrastRatio(rgb1, rgb2) {
const l1 = relativeLuminance(...rgb1);
const l2 = relativeLuminance(...rgb2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
const ratio = contrastRatio([0, 0, 0], [255, 255, 255]);
// 21 (maximum possible — black on white)
const blueOnWhite = contrastRatio([66, 133, 244], [255, 255, 255]);
// ~3.0 — fails AA for normal text, passes for large text
Design Token System
/* tokens.css — define once, use everywhere */
:root {
/* OKLCH palette — perceptually uniform */
--color-brand-50: oklch(0.97 0.03 264);
--color-brand-100: oklch(0.93 0.06 264);
--color-brand-200: oklch(0.87 0.11 264);
--color-brand-300: oklch(0.78 0.16 264);
--color-brand-400: oklch(0.66 0.21 264);
--color-brand-500: oklch(0.55 0.24 264); /* Base brand color */
--color-brand-600: oklch(0.46 0.22 264);
--color-brand-700: oklch(0.38 0.19 264);
--color-brand-800: oklch(0.30 0.15 264);
--color-brand-900: oklch(0.22 0.10 264);
/* Semantic tokens */
--color-text-primary: var(--color-gray-900);
--color-text-secondary: var(--color-gray-600);
--color-text-disabled: var(--color-gray-400);
--color-bg-primary: #ffffff;
--color-bg-secondary: var(--color-gray-50);
--color-bg-tertiary: var(--color-gray-100);
--color-interactive: var(--color-brand-600);
--color-interactive-hover: var(--color-brand-700);
--color-interactive-active: var(--color-brand-800);
--color-danger: oklch(0.55 0.22 25);
--color-warning: oklch(0.72 0.17 85);
--color-success: oklch(0.60 0.18 145);
--color-info: oklch(0.58 0.20 240);
}
Dark Mode Color Strategy
/* Light mode — define with :root */
:root {
--color-text: #0F1419;
--color-bg: #FFFFFF;
--color-surface: #F7F7F7;
--color-border: #E1E4E8;
}
/* Dark mode — override with media query or data attribute */
@media (prefers-color-scheme: dark) {
:root {
--color-text: #F0EDE8;
--color-bg: #0D0F12;
--color-surface: #161A1F;
--color-border: #2D3139;
}
}
/* Or with a class/attribute for user preference override */
[data-theme="dark"] {
--color-text: #F0EDE8;
--color-bg: #0D0F12;
}
Dark mode pitfalls:
- Never simply invert colors —
filter: invert(1)breaks images and branded elements - Dark mode needs higher saturation for colors to appear vivid (screens emit less light)
- Test dark mode with the CSS
@media (prefers-color-scheme: dark)in browser DevTools - Shadows need opacity or lighter colors on dark backgrounds (dark shadow on dark bg is invisible)
Palette Generation Algorithms
Complementary Colors (180° opposite)
function complementary(hue) {
return (hue + 180) % 360;
}
Analogous Colors (30° neighbors)
function analogous(hue) {
return [(hue - 30 + 360) % 360, hue, (hue + 30) % 360];
}
Triadic Colors (120° apart)
function triadic(hue) {
return [hue, (hue + 120) % 360, (hue + 240) % 360];
}
Split-Complementary
function splitComplementary(hue) {
const complement = (hue + 180) % 360;
return [hue, (complement - 30 + 360) % 360, (complement + 30) % 360];
}
Tints and Shades (OKLCH)
function generateScale(hue, chroma = 0.2, steps = 10) {
return Array.from({ length: steps }, (_, i) => {
const lightness = 0.95 - (i / (steps - 1)) * 0.85;
return `oklch(${lightness.toFixed(2)} ${chroma} ${hue})`;
});
}
Color Blindness Considerations
Approximately 8% of males and 0.5% of females have some form of color vision deficiency:
- Deuteranopia (~5%): reduced green sensitivity — red and green appear similar
- Protanopia (~1%): reduced red sensitivity
- Tritanopia (rare): reduced blue sensitivity
- Achromatopsia (very rare): no color vision
Rules for accessible colors:
- Never rely on color alone to convey information — use icons, labels, or patterns
- Test your palette with a color blindness simulator
- Ensure sufficient contrast for all color combinations, not just black/white
- Use textures or patterns to differentiate data series in charts
/* Bad — colorblind users cannot distinguish red from green */
.error { color: red; }
.success { color: green; }
/* Better — add icons and patterns */
.error::before { content: "✕ "; }
.success::before { content: "✓ "; }
.error { color: oklch(0.55 0.22 25); } /* Red with sufficient contrast */
.success { color: oklch(0.45 0.18 145); } /* Darker green for contrast */
CSS Gradient Best Practices
/* Poor gradient — HSL interpolation creates muddy middle */
.gradient-bad {
background: linear-gradient(to right, hsl(0 100% 50%), hsl(120 100% 50%));
}
/* Better — OKLCH interpolation stays vivid */
.gradient-good {
background: linear-gradient(
in oklch to right,
oklch(0.55 0.25 0),
oklch(0.55 0.25 120)
);
}
/* Longer hue path (goes through blue instead of muddy yellow-green) */
.gradient-long-hue {
background: linear-gradient(
in oklch longer hue to right,
oklch(0.55 0.25 0),
oklch(0.55 0.25 120)
);
}
Tools
- Color Converter — convert between HEX, RGB, HSL, OKLCH
- Color Contrast Checker — WCAG AA/AAA compliance testing
- Color Palette Generator — generate harmonious color schemes
- Gradient Generator — build CSS gradients with color space control