
CSS Relative Color Syntax: Dynamic Color Manipulation
Creating color variations has always required preprocessors or JavaScript. The relative color syntax brings powerful color manipulation directly to CSS, letting you modify colors dynamically.
What is Relative Color Syntax?
Relative color syntax allows you to create new colors based on existing ones:
:root {
--primary: #0066cc;
/* Create lighter version */
--primary-light: oklch(from var(--primary) calc(l + 0.2) c h);
/* Create darker version */
--primary-dark: oklch(from var(--primary) calc(l - 0.2) c h);
/* Adjust transparency */
--primary-subtle: oklch(from var(--primary) l c h / 0.2);
} Basic Syntax
/* General pattern */
color-function(from origin-color channel1 channel2 channel3 / alpha)
/* RGB example */
rgb(from blue r g calc(b * 0.5))
/* HSL example */
hsl(from #ff0000 h s calc(l + 10%))
/* OKLCH example (recommended) */
oklch(from var(--color) l c h / 0.5) Why OKLCH is Better
OKLCH provides perceptually uniform colors:
/* HSL: inconsistent lightness */
hsl(0 100% 50%) /* Red */
hsl(60 100% 50%) /* Yellow - looks much brighter! */
hsl(240 100% 50%) /* Blue */
/* OKLCH: consistent perceived lightness */
oklch(0.6 0.25 0) /* Red */
oklch(0.6 0.25 60) /* Yellow - same perceived brightness */
oklch(0.6 0.25 240) /* Blue */ Practical Use Cases
1. Color Scales from Single Color
:root {
--brand: oklch(0.55 0.22 250);
/* Lighter shades */
--brand-50: oklch(from var(--brand) calc(l + 0.4) calc(c * 0.5) h);
--brand-100: oklch(from var(--brand) calc(l + 0.35) calc(c * 0.6) h);
--brand-200: oklch(from var(--brand) calc(l + 0.3) calc(c * 0.7) h);
--brand-300: oklch(from var(--brand) calc(l + 0.2) calc(c * 0.8) h);
--brand-400: oklch(from var(--brand) calc(l + 0.1) calc(c * 0.9) h);
/* Base */
--brand-500: var(--brand);
/* Darker shades */
--brand-600: oklch(from var(--brand) calc(l - 0.1) c h);
--brand-700: oklch(from var(--brand) calc(l - 0.2) c h);
--brand-800: oklch(from var(--brand) calc(l - 0.3) c h);
--brand-900: oklch(from var(--brand) calc(l - 0.4) c h);
} 2. Hover States
.button {
--button-bg: oklch(0.5 0.2 250);
background: var(--button-bg);
color: white;
}
.button:hover {
/* Lighten by 10% */
background: oklch(from var(--button-bg) calc(l + 0.1) c h);
}
.button:active {
/* Darken by 10% */
background: oklch(from var(--button-bg) calc(l - 0.1) c h);
} 3. Transparent Variations
.card {
--card-color: oklch(0.6 0.15 200);
background: var(--card-color);
border: 1px solid oklch(from var(--card-color) l c h / 0.3);
box-shadow: 0 4px 12px oklch(from var(--card-color) l c h / 0.2);
}
.card::before {
/* Subtle gradient overlay */
background: linear-gradient(
to bottom,
oklch(from var(--card-color) calc(l + 0.1) c h / 0),
oklch(from var(--card-color) calc(l - 0.1) c h / 0.5)
);
} 4. Complementary Colors
:root {
--primary: oklch(0.55 0.22 250);
/* Complementary (opposite on color wheel) */
--complementary: oklch(from var(--primary) l c calc(h + 180));
/* Triadic colors */
--triadic-1: oklch(from var(--primary) l c calc(h + 120));
--triadic-2: oklch(from var(--primary) l c calc(h + 240));
/* Analogous colors */
--analogous-1: oklch(from var(--primary) l c calc(h + 30));
--analogous-2: oklch(from var(--primary) l c calc(h - 30));
} Advanced Techniques
Dynamic Theming
:root {
--theme-hue: 220; /* Blue theme */
}
[data-theme="green"] {
--theme-hue: 140;
}
[data-theme="purple"] {
--theme-hue: 280;
}
/* Generate all colors from hue */
:root {
--base-color: oklch(0.6 0.2 var(--theme-hue));
--primary: var(--base-color);
--primary-light: oklch(from var(--base-color) calc(l + 0.2) c h);
--primary-dark: oklch(from var(--base-color) calc(l - 0.2) c h);
--surface: oklch(from var(--base-color) 0.98 calc(c * 0.1) h);
--border: oklch(from var(--base-color) 0.85 calc(c * 0.3) h);
} Accessible Color Contrast
.button {
--bg: oklch(0.5 0.2 250);
background: var(--bg);
/* Ensure readable text: high lightness, low chroma */
color: oklch(from var(--bg) 0.95 calc(c * 0.1) h);
}
.button-outline {
--color: oklch(0.5 0.2 250);
background: transparent;
border: 2px solid var(--color);
color: var(--color);
}
.button-outline:hover {
background: oklch(from var(--color) l c h / 0.1);
border-color: oklch(from var(--color) calc(l - 0.1) c h);
} Gradient Generation
.gradient-card {
--start: oklch(0.7 0.2 300);
background: linear-gradient(
135deg,
var(--start),
oklch(from var(--start) calc(l - 0.2) c calc(h + 30)),
oklch(from var(--start) calc(l - 0.3) calc(c * 1.2) calc(h + 60))
);
} Saturation Adjustments
.image-filter {
--img-color: oklch(0.6 0.2 200);
/* Desaturate */
filter: saturate(0.5);
border: 3px solid oklch(from var(--img-color) l calc(c * 0.3) h);
}
.image-filter:hover {
/* Full saturation */
filter: saturate(1);
border-color: oklch(from var(--img-color) l c h);
} Real-World Example: Design System
:root {
/* Brand colors */
--brand-blue: oklch(0.55 0.22 250);
--brand-green: oklch(0.65 0.2 150);
--brand-red: oklch(0.6 0.25 25);
/* Primary palette from brand blue */
--primary-50: oklch(from var(--brand-blue) 0.97 calc(c * 0.3) h);
--primary-100: oklch(from var(--brand-blue) 0.94 calc(c * 0.4) h);
--primary-200: oklch(from var(--brand-blue) 0.88 calc(c * 0.6) h);
--primary-300: oklch(from var(--brand-blue) 0.78 calc(c * 0.75) h);
--primary-400: oklch(from var(--brand-blue) 0.68 calc(c * 0.85) h);
--primary-500: var(--brand-blue);
--primary-600: oklch(from var(--brand-blue) calc(l - 0.08) c h);
--primary-700: oklch(from var(--brand-blue) calc(l - 0.16) c h);
--primary-800: oklch(from var(--brand-blue) calc(l - 0.24) c h);
--primary-900: oklch(from var(--brand-blue) calc(l - 0.32) c h);
/* Success palette */
--success-light: oklch(from var(--brand-green) calc(l + 0.2) calc(c * 0.6) h);
--success: var(--brand-green);
--success-dark: oklch(from var(--brand-green) calc(l - 0.15) c h);
/* Error palette */
--error-light: oklch(from var(--brand-red) calc(l + 0.2) calc(c * 0.6) h);
--error: var(--brand-red);
--error-dark: oklch(from var(--brand-red) calc(l - 0.15) c h);
/* Surfaces with subtle color tint */
--surface-1: oklch(from var(--brand-blue) 0.99 calc(c * 0.02) h);
--surface-2: oklch(from var(--brand-blue) 0.97 calc(c * 0.05) h);
--surface-3: oklch(from var(--brand-blue) 0.95 calc(c * 0.08) h);
/* Borders */
--border-subtle: oklch(from var(--brand-blue) 0.9 calc(c * 0.1) h);
--border-default: oklch(from var(--brand-blue) 0.8 calc(c * 0.2) h);
--border-strong: oklch(from var(--brand-blue) 0.7 calc(c * 0.3) h);
}
/* Component usage */
.btn-primary {
background: var(--primary-500);
color: oklch(from var(--primary-500) 0.98 calc(c * 0.1) h);
border: 2px solid oklch(from var(--primary-500) calc(l - 0.05) c h);
}
.btn-primary:hover {
background: var(--primary-600);
border-color: var(--primary-700);
}
.btn-primary:focus-visible {
outline: 3px solid oklch(from var(--primary-500) l c h / 0.4);
}
.alert-success {
background: oklch(from var(--success) calc(l + 0.35) calc(c * 0.3) h);
border-left: 4px solid var(--success);
color: var(--success-dark);
}
.badge-error {
background: var(--error);
color: oklch(from var(--error) 0.98 calc(c * 0.2) h);
box-shadow: 0 2px 4px oklch(from var(--error) l c h / 0.3);
} Browser Support
Relative color syntax support is growing:
- Chrome/Edge: ✅ v119+ (November 2023)
- Firefox: ❌ Not yet supported (in development)
- Safari: ✅ v16.4+ (March 2023)
Current global support is ~50%, requiring fallbacks for production.
Progressive Enhancement
Provide fallback colors:
.button {
/* Fallback */
background: #0066cc;
/* Enhanced */
background: oklch(from var(--primary) l c h);
}
/* Feature detection */
@supports (background: oklch(from red l c h)) {
:root {
--primary-light: oklch(from var(--primary) calc(l + 0.2) c h);
}
} CSS Variables with Fallbacks
:root {
--primary: #0066cc;
/* Fallback manually created shades */
--primary-light: #3385db;
--primary-dark: #0052a3;
}
@supports (background: oklch(from red l c h)) {
:root {
/* Override with relative colors */
--primary-light: oklch(from var(--primary) calc(l + 0.2) c h);
--primary-dark: oklch(from var(--primary) calc(l - 0.2) c h);
}
} Color Space Comparison
RGB/Hex
/* Limited precision, non-perceptual */
#0066cc
rgb(0 102 204) HSL
/* Perceptually non-uniform */
hsl(210 100% 40%) OKLCH
/* Perceptually uniform, wide gamut */
oklch(0.55 0.22 250)
/* L: lightness (0-1) */
/* C: chroma (0-0.4) */
/* H: hue (0-360) */ Common Pitfalls
1. Exceeding Color Gamut
/* ❌ Chroma too high, color clamps */
oklch(from var(--color) l 0.8 h)
/* ✅ Keep chroma reasonable (< 0.4) */
oklch(from var(--color) l calc(c * 1.2) h) 2. Lightness Out of Bounds
/* ❌ Can exceed 0-1 range */
oklch(from var(--dark) calc(l + 0.8) c h)
/* ✅ Clamp values */
oklch(from var(--dark) min(calc(l + 0.3), 0.95) c h) 3. Forgetting Alpha Channel
/* ❌ Won't work as expected */
oklch(from var(--color) l c h 0.5)
/* ✅ Use slash for alpha */
oklch(from var(--color) l c h / 0.5) Combining with Other Features
With Custom Properties
:root {
--base-l: 0.6;
--base-c: 0.2;
--base-h: 250;
--color: oklch(var(--base-l) var(--base-c) var(--base-h));
--color-light: oklch(calc(var(--base-l) + 0.2) var(--base-c) var(--base-h));
} With Container Queries
.card {
container-type: inline-size;
--card-color: oklch(0.6 0.2 220);
}
@container (min-width: 400px) {
.card {
background: oklch(from var(--card-color) calc(l + 0.1) c h);
}
} With Color-Mix (Alternative)
/* Relative colors */
background: oklch(from blue calc(l + 0.2) c h);
/* Color-mix alternative */
background: color-mix(in oklch, blue, white 20%); Tools and Resources
Converting Existing Colors
/* Hex to OKLCH */
/* Use tools like: https://oklch.com */
#0066cc → oklch(0.55 0.22 250) Visualizing Color Spaces
Use browser DevTools color picker to see OKLCH values and convert between formats.
Accessibility Considerations
Ensure sufficient contrast:
.text-on-primary {
--bg: oklch(0.5 0.2 250);
background: var(--bg);
/* Ensure 4.5:1 contrast ratio */
color: oklch(from var(--bg) 0.95 calc(c * 0.1) h);
}
/* Check contrast programmatically */
@media (prefers-contrast: more) {
.text-on-primary {
color: oklch(from var(--bg) 0.98 calc(c * 0.05) h);
}
} Conclusion
Relative color syntax revolutionizes how we work with colors in CSS. It enables:
- Dynamic color scales
- Automatic theming
- Consistent hover states
- Accessible color variations
- Single-source-of-truth design systems
While browser support is still growing, progressive enhancement makes it usable today. Combined with OKLCH’s perceptual uniformity, relative colors represent the future of CSS color manipulation.
Browser support snapshot
Live support matrix for css-relative-colors from
Can I Use.
Show static fallback image

Source: caniuse.com









