Introduction to CSS Color Module Level 4
CSS Color Module Level 4 represents the most significant update to CSS color handling since the specification's inception. As a W3C Candidate Recommendation Draft published in April 2025, this module introduces a comprehensive suite of new color functions, syntax improvements, and device-independent color spaces that enable web developers to work with colors that were previously impossible to express in CSS.
The specification addresses a fundamental limitation of previous CSS color models: the inability to specify colors outside the sRGB gamut, even when display devices support wider color ranges. As display technology has advanced--with many modern screens now capable of displaying the P3 color space, which encompasses approximately 50% more colors than sRGB--CSS has needed to evolve to take advantage of these capabilities.
Our web development services team regularly implements these modern color techniques to create visually stunning and accessible interfaces that take full advantage of today's display technology.
What Makes Level 4 Different from Level 3
CSS Color Module Level 3 established the foundational color syntaxes that most web developers are familiar with today: rgb(), rgba(), hsl(), and hsla() functions, along with hex notation and named colors. While these remain valid and continue to work, Level 4 introduces several paradigm-shifting additions:
- Device-independent color spaces (Lab, LCH, Oklab, OKLCh) that map directly to human perception
- Wide-gamut color spaces (display-p3, a98-rgb, prophoto-rgb, rec2020) that unlock access to colors beyond sRGB
- Modernized syntax that deprecates comma-separated values in favor of space-separated values
- Relative color syntax for manipulating existing colors dynamically
- color-mix() function for programmatic color blending
Modern Syntax
Space-separated values and slash notation for alpha transparency replace comma-separated legacy syntax
OKLCh Color Space
Perceptually uniform colors that enable predictable color manipulation and wider gamut support
Wide Gamut Support
Access to display-p3, a98-rgb, prophoto-rgb, and rec2020 color spaces for vibrant colors
Relative Color Syntax
Modify existing colors using from keyword, creating dynamic color variations without JavaScript
color-mix() Function
Blend two colors programmatically in any color space for theming and design systems
HWB Colors
Hue, Whiteness, Blackness model provides intuitive color specification for tints and shades
Modern Color Syntax
The Evolution from Comma-Separated to Space-Separated Syntax
One of the most noticeable changes in CSS Color Module Level 4 is the introduction of a modern syntax that uses space-separated values instead of commas. This change aligns CSS color functions with the general CSS syntax convention and improves readability and consistency across the specification.
In Level 3, color functions required commas to separate components, making the syntax verbose and inconsistent with other CSS declarations. The legacy comma-separated syntax served developers well for years but created visual noise when specifying colors with alpha transparency, as the comma separating RGB components had to compete with the comma separating color from opacity.
Level 3 legacy syntax:
/* Comma-separated values */
color: rgb(255, 0, 0);
color: rgba(255, 0, 0, 0.5);
color: hsl(120, 100%, 50%);
color: hsla(120, 100%, 50%, 0.75);
Level 4 modern syntax:
/* Space-separated values */
color: rgb(255 0 0);
color: rgb(255 0 0 / 0.5);
color: hsl(120 100% 50%);
color: hsl(120 100% 50% / 0.75);
The new syntax removes the requirement for commas between color components while maintaining backward compatibility with the legacy approach. This modernization reduces visual clutter and creates a more intuitive connection between color specification and other CSS value declarations.
The Slash Notation for Alpha
A particularly significant addition is the slash notation for specifying alpha transparency. Rather than using rgba() or hsla() functions--which always required four values, even when alpha was 1 (fully opaque)--developers can now use the slash to indicate opacity within any color function. This unified approach means you can specify alpha transparency uniformly across all color functions, regardless of which color space you're working in.
/* Modern alpha notation with slash */
color: rgb(255 0 0 / 0.5); /* 50% opacity red */
color: hsl(120 100% 50% / 0.75); /* 75% opacity green */
color: oklch(70% 0.15 120 / 0.8); /* 80% opacity in OKLCh */
color: lab(50% -20 30 / 0.3); /* 30% opacity in Lab */
Comparing old and new alpha syntax:
/* Old rgba() / hsla() syntax */
color: rgba(255, 0, 0, 0.5);
color: hsla(240, 100%, 50%, 0.8);
/* New slash notation */
color: rgb(255 0 0 / 0.5);
color: hsl(240 100% 50% / 0.8);
This unified approach eliminates the need to remember separate function names for alpha variants and works consistently across all color functions, including the new device-independent color spaces like OKLCh and Lab.
Legacy Syntax Compatibility
Importantly, the comma-separated legacy syntax remains valid and will continue to work in all browsers. The Level 4 specification defines both syntaxes as valid, allowing developers to migrate gradually and maintain backward compatibility. However, the modern syntax is recommended for new projects and provides a clearer path to adopting additional Level 4 features like relative color syntax and color-mix().
Device-Independent Color Spaces
Understanding Perceptual Uniformity
Traditional RGB color models, while computationally convenient, do not correspond to how humans perceive color. In RGB space, the same numerical difference in component values can produce vastly different perceived color differences depending on where in the color space you are measuring. This non-uniformity makes it difficult to create consistent color relationships and transitions, especially when animating between colors or creating color palettes with predictable spacing.
Device-independent color spaces like Lab and OKLCh are designed to model human color perception more accurately. In these perceptually uniform spaces, equal numerical distances correspond to approximately equal perceptual differences, making them ideal for tasks like creating color palettes with consistent visual spacing or animating colors smoothly without unexpected hue shifts or muddy intermediate colors.
The CIE Lab and LCH Color Spaces
The CIE Lab color space (often simply called Lab) and its cylindrical counterpart LCH were standardized by the International Commission on Illumination (CIE) and provide a perceptually uniform color representation. These color spaces were originally developed for color science applications and have now been adopted by CSS for web development.
Lab color components:
- L (Lightness): A percentage from 0% (black) to 100% (white), representing the visual lightness of the color
- a: The green-red axis, where negative values are green and positive values are red (typical range: -128 to +128)
- b: The blue-yellow axis, where negative values are blue and positive values are yellow (typical range: -128 to +128)
/* Lab color space */
color: lab(50% -20 30); /* Medium lightness, green tint, yellow tint */
color: lab(50% -20 30 / 0.5); /* Same with 50% opacity */
color: lab(65% 20 -40); /* Lighter, red tint, blue tint */
LCH (Lightness, Chroma, Hue) is the cylindrical representation of Lab, separating chromatic information into chroma (saturation) and hue (color angle) components:
- L (Lightness): Same as Lab, 0% to 100%
- C (Chroma): The saturation or intensity of the color (theoretical range is unbounded, but practical values depend on the display)
- H (Hue): The color angle in degrees, 0 to 360 around the color wheel
/* LCH (cylindrical Lab) */
color: lch(50% 35 120); /* Medium lightness, moderate saturation, green hue */
color: lch(50% 35 120 / 0.8); /* Same with 80% opacity */
color: lch(65% 60 250); /* Lighter, more saturated, blue-purple hue */
LCH is often preferred over Lab for certain tasks because it separates chroma (saturation) and hue into distinct components, making it easier to reason about color relationships intuitively. You can increase chroma to make a color more vivid, or rotate hue to shift the color around the color wheel, while keeping the other components constant.
The OKLCh Color Space
OKLCh represents the current state-of-the-art in perceptually uniform color spaces for CSS. Developed by Björn Ottosson, OKLCh offers even better perceptual uniformity than CIE LCH while maintaining computational efficiency. This color space has become the recommended choice for new projects that want to take advantage of Level 4 color capabilities.
OKLCh components:
- L (Lightness): A percentage from 0% to 100%, representing the perceived lightness
- C (Chroma): A number representing saturation (theoretical range is unbounded, but values above approximately 0.5 often result in out-of-gamut colors for typical displays)
- H (Hue): The color angle in degrees, 0 to 360
/* OKLCh color space - the recommended modern choice */
color: oklch(60% 0.15 250); /* 60% lightness, moderate chroma, blue hue */
color: oklch(70% 0.2 120 / 0.75); /* 70% lightness, 75% opacity, green hue */
color: oklch(50% 0.3 30); /* 50% lightness, higher saturation, orange hue */
The OKLCh syntax uses percentages for lightness (0% to 100%), a number for chroma (where typical useful values range from 0 to around 0.4 for sRGB displays, with higher values reserved for wide gamut displays), and degrees for hue (0 to 360). The chroma value in OKLCh is normalized differently than LCH, resulting in smaller numerical values for equivalent saturation levels.
Why OKLCh Matters for Web Development
The adoption of OKLCh in CSS Color Module Level 4 is significant because it enables several workflows that were previously difficult or impossible with RGB-based color models:
1. Predictable Color Manipulation: When you increase or decrease chroma in OKLCh, the visual effect is consistent regardless of the hue or lightness value. This predictability makes it easier to create harmonious color palettes where each shade has the same perceived saturation level. In RGB, the same numerical change in saturation can produce vastly different visual results depending on the color.
2. Wider Color Gamut: OKLCh can represent colors beyond the sRGB gamut, including the full P3 color space on supported displays. This enables more vibrant, saturated colors that were previously unavailable in CSS and appear clipped when using traditional RGB notation.
3. Smoother Animations: Because OKLCh is perceptually uniform, animating between colors produces smoother visual results than animating RGB values. RGB animations often pass through muddy or unexpected intermediate colors due to the non-linear relationship between RGB values and human perception. OKLCh interpolation produces more natural-looking transitions.
4. Improved Developer Experience: The OKLCh syntax is often more intuitive than RGB, as developers can think in terms of "lighter/darker," "more/less saturated," and "hue rotation" directly in their CSS. The relative color syntax works naturally with OKLCh, allowing you to reference and modify color components by their meaningful names (l, c, h).
Wide Gamut Color Spaces
The Display P3 Color Space
Display P3 is a wide gamut color space developed by Apple that encompasses approximately 45% more colors than sRGB. It has become the de facto standard for wide gamut content on the web and is supported by most modern displays, including those in Apple devices (iPhones, iPads, MacBooks with Retina displays) and high-end Windows monitors with wide gamut panels. The P3 color space represents a significant step forward in displaying vibrant, saturated colors that appear more vivid and lifelike.
The adoption of Display P3 in web development opens up new possibilities for creating visually stunning interfaces that take advantage of modern display technology. Professional photographers, video editors, and designers who work with color-critical applications have been using P3 for years, and now web developers can access the same vibrant color palette directly in CSS.
/* Display P3 color space - approximately 45% more colors than sRGB */
color: color(display-p3 1 0 0); /* Pure red, more saturated than sRGB */
color: color(display-p3 0.95 0.1 0.3 / 0.8); /* 80% opacity coral color */
color: color(display-p3 0 1 0); /* Pure green, significantly more vibrant */
The color() function accepts a color space name followed by component values normalized to the 0-1 range. For Display P3, each component (red, green, blue) is specified as a decimal between 0 and 1, where 1 represents the full intensity of that channel in the P3 color space.
Other Wide Gamut Spaces
CSS Color Module Level 4 defines several additional wide gamut color spaces, each with different characteristics suited to specific use cases. Understanding these options helps developers choose the appropriate color space for their specific requirements, whether for web design, photography, or archival purposes.
A98 RGB (Adobe 1998 RGB): Developed by Adobe, this color space offers a wider gamut than sRGB, particularly in the green and cyan regions. It has been widely adopted in professional photography workflows and is commonly used for images that will be printed or displayed on wide gamut displays. A98 RGB provides about 30% more color volume than sRGB while maintaining good compatibility with existing workflows.
ProPhoto RGB: An extremely wide gamut space developed by Kodak that can represent over 90% of surface colors that humans can perceive. This expansive gamut makes ProPhoto RGB useful for archival purposes and professional photography where preserving the full range of captured colors is essential. However, its extremely wide gamut means that colors can appear significantly different on sRGB displays, requiring careful color management.
Rec. 2020: The ITU-R Recommendation for ultra-high-definition television (UHDTV), offering a very wide gamut that approaches what the human eye can perceive. Rec. 2020 is the color space standard for 4K and 8K video content. Currently, few consumer displays can render the full Rec. 2020 gamut, but support is growing in high-end televisions and monitors.
/* Additional wide gamut color spaces */
color: color(a98-rgb 1 0 0); /* A98 RGB red */
color: color(prophoto-rgb 1 0 0); /* ProPhoto RGB red */
color: color(rec2020 1 0 0); /* Rec. 2020 red */
When choosing between these color spaces, consider your target audience and their display capabilities. Display P3 offers the best balance of wide gamut coverage and current device support, while Rec. 2020 represents the future of ultra-wide color for next-generation displays.
Using the color() Function
The color() function provides access to any of these predefined color spaces, as well as standard spaces like sRGB and sRGB-linear. This function is the gateway to wide gamut colors and allows developers to specify colors in color spaces beyond the traditional sRGB.
/* Standard color spaces */
color: color(srgb 1 0 0); /* sRGB red (equivalent to rgb(255 0 0)) */
color: color(srgb-linear 1 0 0); /* Linear sRGB for physics-based rendering */
/* Wide gamut color spaces */
color: color(display-p3 1 0 0); /* Display P3 red */
color: color(a98-rgb 1 0 0); /* A98 RGB red */
color: color(prophoto-rgb 1 0 0); /* ProPhoto RGB red */
color: color(rec2020 1 0 0); /* Rec. 2020 red */
/* XYZ color spaces for color management */
color: color(xyz-d50 1 0 0); /* XYZ D50 */
color: color(xyz-d65 1 0 0); /* XYZ D65 */
color: color(xyz 1 0 0); /* XYZ (D65 implied) */
Practical Considerations for Wide Gamut
When working with wide gamut color spaces, developers should be aware that colors specified in these spaces may appear clipped or shifted on displays that cannot render them. The specification defines gamut mapping algorithms that browsers should use to handle out-of-gamut colors, but the visual result can vary between browsers and devices. A common practice is to provide a fallback in sRGB for older displays while specifying wide gamut colors for capable devices.
/* Progressive enhancement for wide gamut */
.vibrant-element {
color: oklch(70% 0.2 320); /* Fallback in OKLCh */
color: color(display-p3 1 0 0.5); /* Wide gamut for supported displays */
}
/* Using media queries for wide gamut detection */
@media (color-gamut: p3) {
.wide-gamut-only {
color: color(display-p3 0 0.8 0.8);
}
}
Relative Color Syntax
Introduction to Relative Color Syntax
The relative color syntax is one of the most powerful features in CSS Color Module Level 4, fundamentally changing how developers work with colors in CSS. This feature allows you to create new colors by modifying existing colors--extracting components, performing calculations, and producing variations--all without JavaScript. The relative color syntax works by using the from keyword to specify a source color, then defining a new color function with modified components.
Before this feature, creating color variations required either preprocessor functions, hardcoded values, or JavaScript. Now, you can reference and modify color components directly in CSS, making it possible to create sophisticated theming systems using CSS custom properties alone.
/* Extract and modify color components using relative syntax */
color: oklch(from var(--brand-color) l 0.8); /* Same hue and chroma, 80% lightness */
color: oklch(from var(--brand-color) l calc(l + 0.1)); /* Lighten by 10% */
color: oklch(from var(--brand-color) h calc(h + 30deg)); /* Rotate hue 30 degrees */
color: oklch(from var(--brand-color) c 0.05); /* Reduce chroma to 0.05 */
/* Create variations with transparency */
color: oklch(from var(--brand-color) l 0.95 / 0.3); /* Light variant with 30% opacity */
Syntax and Component References
The relative color syntax uses the from keyword to specify a source color, followed by the desired color function and the modified components. The original color's components can be referenced by their names, allowing you to perform calculations or use fixed values. The component names depend on the target color function: l, c, h for OKLCh/LCH; l, a, b for Lab; r, g, b, a for RGB.
Lightening a color:
/* Extract lightness and increase it */
--lighter: oklch(from var(--base) l calc(l + 0.15));
/* Set lightness to a specific value */
--light: oklch(from var(--base) l 0.85);
Desaturating a color:
/* Reduce chroma to create a muted version */
--muted: oklch(from var(--base) c calc(c * 0.3));
/* Create a completely desaturated version */
--gray: oklch(from var(--base) c 0);
Rotating hue:
/* Create complementary color (180 degree rotation) */
--complementary: oklch(from var(--base) h calc(h + 180deg));
/* Subtle hue shift for related colors */
--analogous: oklch(from var(--base) h calc(h + 30deg));
Creating transparent variants:
/* Semi-transparent surface color */
--surface: oklch(from var(--base) l 0.98 / 0.5);
/* Fully transparent version */
--transparent: oklch(from var(--base) l 0 c h / 0);
Practical Applications
Relative color syntax is particularly powerful for theming and design systems, where you need to create consistent color relationships. By defining a base color and generating all variations from it, you ensure that colors always work together harmoniously. Any change to the base color automatically propagates to all derived colors.
/* Design system color tokens using relative syntax */
:root {
--primary: oklch(65% 0.2 250);
/* Light variants for hover states and highlights */
--primary-light: oklch(from var(--primary) l calc(l + 0.15));
--primary-lighter: oklch(from var(--primary) l calc(l + 0.25));
/* Dark variants for active states and text */
--primary-dark: oklch(from var(--primary) l calc(l - 0.15));
--primary-darker: oklch(from var(--primary) l calc(l - 0.25));
/* Pale versions for backgrounds */
--primary-pale: oklch(from var(--primary) l 0.95 / 0.15);
--primary-subtle: oklch(from var(--primary) l 0.97 / 0.08);
/* Foreground colors for contrast */
--primary-foreground: oklch(from var(--primary) l 0.98);
--primary-foreground-dark: oklch(from var(--primary) l 0.2);
}
/* Using relative colors in components */
.button-primary {
background: var(--primary);
color: var(--primary-foreground);
border: 1px solid var(--primary-dark);
}
.button-primary:hover {
background: var(--primary-light);
border-color: var(--primary);
}
.button-primary:active {
background: var(--primary-dark);
}
.card {
background: var(--primary-pale);
border-left: 4px solid var(--primary);
}
Cross-Color-Space Manipulation
The relative color syntax also allows converting between color spaces during manipulation, enabling powerful workflows that weren't previously possible in pure CSS. You can take a color in one space and transform it to another, all while modifying specific components.
/* Convert RGB to OKLCh and adjust */
--adjusted: oklch(from var(--rgb-color) l c h);
/* Convert to Lab, then modify */
--lab-adjusted: lab(from var(--oklch-color) l calc(l * 1.1) b);
/* Create a harmonious palette from a base color */
--palette-1: oklch(from var(--base) l 0.6 c 0.15 h 0); /* Orange */
--palette-2: oklch(from var(--base) l 0.6 c 0.15 h 72); /* Yellow-green */
--palette-3: oklch(from var(--base) l 0.6 c 0.15 h 144); /* Green */
--palette-4: oklch(from var(--base) l 0.6 c 0.15 h 216); /* Cyan-blue */
--palette-5: oklch(from var(--base) l 0.6 c 0.15 h 288); /* Purple */
The color-mix() Function
Basic color-mix() Syntax
The color-mix() function enables programmatic color blending directly in CSS, eliminating the need for preprocessor functions, JavaScript, or design tools for simple color mixing. This function takes two colors and blends them together in a specified color space, producing a new color that represents the mixture. The syntax is straightforward: specify the color space with in, then provide the two colors to mix, optionally with percentages to control the ratio.
The function accepts several arguments:
- The color space for interpolation (specified with
in) - The first color, optionally with a percentage indicating its weight
- The second color, optionally with a percentage indicating its weight
/* Mix two colors equally (50/50) */
background: color-mix(in oklch, var(--color1), var(--color2));
/* Mix with specific ratio - 80% color1, 20% color2 */
background: color-mix(in oklch, var(--color1) 80%, var(--color2) 20%);
/* Mix in different color spaces */
background: color-mix(in srgb, #ff0000, #0000ff);
background: color-mix(in oklch, #ff0000, #0000ff);
background: color-mix(in hsl, #ff0000, #0000ff);
background: color-mix(in lch, #ff0000, #0000ff);
/* Color can be specified without space, then percentages apply to subsequent colors */
background: color-mix(in oklch, #ff0000 30%, #0000ff 70%);
Choosing the Interpolation Color Space
The color space specified after in determines how colors are mixed, significantly affecting the result. Different color spaces produce noticeably different results when mixing the same colors, because the interpolation path through each color space varies. Perceptually uniform spaces like oklch typically produce more natural-looking blends that don't pass through muddy grays or unexpected hues.
/* Different interpolation spaces produce different results */
.mix-srgb { background: color-mix(in srgb, #ff0000, #0000ff); }
.mix-oklch { background: color-mix(in oklch, #ff0000, #0000ff); }
.mix-hsl { background: color-mix(in hsl, #ff0000, #0000ff); }
.mix-lch { background: color-mix(in lch, #ff0000, #0000ff); }
When mixing red and blue:
- sRGB: Produces a purplish-gray in the middle, as RGB interpolation doesn't follow perceptual lines
- oklch: Produces a natural purple, following the shortest perceptual path between colors
- hsl: Produces a saturated purple but can have unexpected behavior near color wheel boundaries
- lch: Similar to oklch but with different chroma handling
For most use cases, oklch is recommended as it provides the most natural-looking blends while supporting wide gamut colors.
Practical Applications
The color-mix() function is valuable for creating themes, states, and color relationships without preprocessor macros or build tools. It works seamlessly with CSS custom properties, making it ideal for dynamic theming based on user preferences or design tokens.
/* Define brand colors */
:root {
--brand-primary: #0066cc;
--brand-secondary: #ff6600;
/* Create themed variants using color-mix */
--primary-subtle: color-mix(in oklch, var(--brand-primary), white 90%);
--primary-muted: color-mix(in oklch, var(--brand-primary), black 70%);
--secondary-light: color-mix(in oklch, var(--brand-secondary), white 70%);
--secondary-dark: color-mix(in oklch, var(--brand-secondary), black 40%);
/* Interactive states */
--primary-hover: color-mix(in oklch, var(--brand-primary), #003366 20%);
--primary-active: color-mix(in oklch, var(--brand-primary), #003366 40%);
}
/* Dynamic theming with prefers-color-scheme */
@media (prefers-color-scheme: dark) {
.surface {
background: color-mix(in oklch, var(--surface-base), black 30%);
border-color: color-mix(in oklch, var(--surface-base), black 50%);
}
}
/* Accessible focus rings */
.button:focus-visible {
outline: 2px solid color-mix(in oklch, var(--primary), white 30%);
outline-offset: 2px;
}
/* Gradient-like effects without CSS gradients */
.overlay {
background: color-mix(in oklch, var(--overlay-color), transparent 50%);
}
The HWB Color Function
Understanding HWB
The HWB (Hue, Whiteness, Blackness) color model provides an intuitive way to specify colors by starting from a hue and then adding white and black to achieve the desired saturation and lightness. This model maps closely to how artists think about mixing colors, making it more intuitive than RGB for many design tasks. HWB was originally proposed as an intuitive alternative to RGB for color pickers and has now been standardized in CSS Color Module Level 4.
HWB components:
- Hue (H): The base color angle in degrees, 0 to 360, representing the position on the color wheel
- Whiteness (W): The amount of white to mix in, as a percentage from 0% to 100%
- Blackness (B): The amount of black to mix in, as a percentage from 0% to 100%
The whiteness and blackness values add up to determine the final saturation and lightness. When you add white, you create a tint of the original hue. When you add black, you create a shade. Adding both white and black creates tones.
/* HWB syntax examples */
color: hwb(120 0% 0%); /* Pure green - no white or black added */
color: hwb(120 50% 0%); /* 50% white - light, pastel green */
color: hwb(120 0% 50%); /* 50% black - dark, forest green */
color: hwb(120 50% 50%); /* 50% white, 50% black = medium gray-green */
color: hwb(120 20% 60%); /* Light, desaturated green */
color: hwb(120 0% 100%); /* Pure black - all black, no matter the hue */
color: hwb(120 100% 0%); /* Pure white - all white, no matter the hue */
color: hwb(120 80% 10%); /* Mostly white with slight black = very pale green */
Understanding the interaction of W and B:
- When W + B = 0%: You get the pure, fully saturated hue
- When W + B = 100%: You get a neutral gray (regardless of hue)
- When W > 0% and B = 0%: You get tints (lighter versions)
- When W = 0% and B > 0%: You get shades (darker versions)
- When W > 0% and B > 0%: You get tones (desaturated versions)
This intuitive model makes it easy to create systematic color palettes. Simply choose a base hue, then vary the whiteness and blackness to create a full range of tints, shades, and tones.
HWB Advantages
HWB is particularly useful when you want to create tints (adding white) and shades (adding black) of a color, as the syntax directly expresses these operations. Rather than thinking in terms of saturation and lightness (as in HSL), you think in terms of how much white or black to add to the pure color.
/* Create a complete color palette using HWB */
--hue: 200;
.color-50 { color: hwb(var(--hue) 80% 0%); } /* Very light tint */
.color-100 { color: hwb(var(--hue) 60% 0%); } /* Light tint */
.color-200 { color: hwb(var(--hue) 40% 0%); } /* Medium-light tint */
.color-300 { color: hwb(var(--hue) 20% 0%); } /* Slightly light */
.color-400 { color: hwb(var(--hue) 0% 0%); } /* Pure hue */
.color-500 { color: hwb(var(--hue) 0% 20%); } /* Slightly dark */
.color-600 { color: hwb(var(--hue) 0% 40%); } /* Medium-dark shade */
.color-700 { color: hwb(var(--hue) 0% 60%); } /* Dark shade */
.color-800 { color: hwb(var(--hue) 0% 80%); } /* Very dark shade */
.color-900 { color: hwb(var(--hue) 0% 90%); } /* Near black */
HWB simplifies color palette creation because you don't need to calculate saturation and lightness percentages. Instead, you directly control whiteness and blackness, which maps more naturally to how designers think about color relationships.
Accessibility Considerations
WCAG Color Requirements
When using the expanded color capabilities in CSS Color Module Level 4, developers must consider accessibility requirements defined in WCAG 2.1 guidelines. These requirements ensure that text remains readable and that information is not conveyed through color alone, making web interfaces usable by people with various visual impairments, including color blindness. Proper color contrast is a fundamental aspect of accessible web design that benefits all users.
Contrast ratio requirements (Level AA):
- Normal text: Minimum contrast ratio of 4.5:1 between text color and background color
- Large text (18pt or 14pt bold): Minimum contrast ratio of 3:1 between text color and background color
Contrast ratio requirements (Level AAA):
- Normal text: Minimum contrast ratio of 7:1
- Large text: Minimum contrast ratio of 4.5:1
/* Example: Ensuring adequate contrast */
.text-primary {
color: #1a1a2e;
background: #ffffff;
/* Contrast ratio of approximately 16:1 - well above 4.5:1 requirement */
}
.headline-large {
color: #2d3436;
background: #f5f6fa;
/* Contrast ratio of approximately 8:1 - meets Level AAA */
}
/* Warning: Insufficient contrast */
.text-warning {
color: #a0a0a0; /* Light gray text */
background: #d0d0d0; /* Light gray background */
/* Contrast ratio of approximately 1.6:1 - FAILS WCAG requirements */
}
Testing Color Contrast
With the expanded color capabilities in Level 4--including device-independent colors, wide gamut spaces, and programmatic color mixing--it's crucial to verify that color combinations remain accessible across different displays and viewing conditions. Color contrast should be tested in the target color space for the most accurate results.
/* Use CSS custom properties for consistent contrast testing */
:root {
--foreground: #1a1a2e;
--background: #ffffff;
/* Test contrast in the intended color space */
}
/* Large text has slightly lower requirements */
.headline {
color: var(--foreground);
background: var(--background);
/* Verify contrast >= 3:1 for large text */
}
/* Interactive elements need clear distinction */
.button {
background: var(--primary);
color: var(--primary-foreground);
/* Focus states need even higher contrast */
}
.button:focus-visible {
outline: 3px solid var(--focus-color);
outline-offset: 2px;
}
Color Independence
WCAG Success Criterion 1.4.1 states that color should not be used as the only visual means of conveying information. This means that status indicators, form validation messages, and other important information must have additional visual indicators such as icons, text labels, or patterns--not just color differences.
/* Good: Multiple indicators beyond color */
.status-success {
color: #006600;
background: #e6ffe6;
border-left: 4px solid #006600;
/* Has: color, background contrast, AND border indicator */
}
.status-error {
color: #cc0000;
background: #ffe6e6;
border-left: 4px solid #cc0000;
/* Has: color, background contrast, AND border indicator */
}
/* Good: Icon alongside color */
.required-field::after {
content: "*";
color: #cc0000;
margin-left: 0.25em;
/* The asterisk provides a non-color indicator */
}
/* Bad: Color alone */
.status-warning {
color: #ff9900;
/* Only using color to indicate warning - FAILS WCAG */
}
When using wide gamut colors, be aware that users with color blindness may perceive colors differently. Always provide additional indicators for status messages, form validation, and any information that relies on color distinction.
The none Keyword and Color Component Handling
Understanding the none Keyword
CSS Color Module Level 4 introduces the none keyword, which allows specification of missing color components. This is particularly useful when working with relative color syntax or when you want to let the browser calculate certain components based on the color space or source color. The none keyword provides a clear, explicit way to indicate that a particular component should be computed rather than specified directly.
The none keyword is especially valuable in relative color syntax, where you might want to modify only some components of a color while preserving others. Instead of referencing the original component with a calculation, you can use none to explicitly indicate that the browser should determine the appropriate value.
/* Let browser calculate specific components */
color: oklch(70% none 120); /* Calculate chroma, specify L and H */
color: lab(50% none 30); /* Calculate a channel, specify L and b */
color: lch(none 40 120); /* Calculate lightness, specify C and H */
color: rgb(255 none 0); /* Calculate green channel */
Practical Applications
The none keyword is most useful when you want to modify only some components of a color while leaving others at their default or calculated values. This provides flexibility in color manipulation and can simplify code when you don't need to reference the source color's components.
/* Keep original lightness and chroma, change hue to 45 degrees */
color: oklch(from var(--base) l c 45deg);
/* Keep original lightness and hue, reduce chroma */
color: oklch(from var(--base) l calc(c * 0.5) h);
/* Lighten while preserving hue and chroma */
color: oklch(from var(--base) calc(l * 1.1) none none);
/* Set chroma to maximum while preserving L and H */
color: oklch(from var(--base) l 0.5 none 90deg);
/* Convert to grayscale while preserving lightness */
color: oklch(from var(--base) l 0 0 h); /* c 0 = no saturation */
/* Calculate hue from original while setting new L and C */
color: oklch(from var(--base) calc(l * 0.9) 0.15 none);
The none keyword makes the intent of your color declarations explicit and can improve code readability by clearly indicating which components are being calculated versus which are specified directly.
Gradient Improvements
Hue Interpolation in Gradients
CSS gradients can now specify how hue should be interpolated between colors, preventing unexpected color shifts and "gray zones" that occur with the default interpolation method. When creating gradients between colors that are far apart on the color wheel, the default interpolation can produce muddy, desaturated intermediate colors that don't match the intended visual transition.
The default RGB-based interpolation doesn't follow the perceptual properties of color, so gradients from red to blue, for example, can pass through a grayish-purple region rather than a vibrant purple. Level 4 provides control over the interpolation color space and hue interpolation method, enabling smoother, more predictable gradient transitions.
/* Default interpolation (RGB-based, can produce gray zones) */
background: linear-gradient(to right, red, blue);
/* Specified hue interpolation using color space keywords */
background: linear-gradient(to right in hsl, red, blue);
background: linear-gradient(to right in hsl longer hue, red, blue);
background: linear-gradient(to right in oklch, red, blue);
background: linear-gradient(to right in lch, red, blue);
Hue Interpolation Options
Four hue interpolation methods are available, controlling how the browser navigates around the color wheel when interpolating between hues:
- shorter (default): Takes the shortest path around the color wheel, crossing through 0° if necessary
- longer: Takes the longer path around the color wheel, useful for creating rainbow effects
- increasing: Always increases hue value, wrapping from 360° to 0° if needed
- decreasing: Always decreases hue value, wrapping from 0° to 360° if needed
/* Shorter path (default) - takes the most direct route */
.shorter {
background: linear-gradient(in hsl shorter hue, red, blue);
/* Goes through purple, ~270° rotation */
}
/* Longer path - goes the long way around */
.longer {
background: linear-gradient(in hsl longer hue, red, blue);
/* Goes through yellow and green, ~450° rotation */
}
/* Increasing - always increases hue */
.increasing {
background: linear-gradient(in hsl increasing, red, blue);
/* Passes through orange, yellow, green, cyan */
}
/* Decreasing - always decreases hue */
.decreasing {
background: linear-gradient(in hsl decreasing, red, blue);
/* Passes through magenta */
}
Benefits for Design
These options enable smoother, more predictable gradient transitions that follow perceptual color relationships rather than mathematical RGB interpolation. Perceptually uniform color spaces like oklch produce the most natural-looking gradients, especially for subtle color transitions in UI design.
/* Smooth rainbow transition around the color wheel */
.rainbow {
background: linear-gradient(in oklch longer hue,
red, orange, yellow, green, blue, violet
);
}
/* Subtle UI gradient using oklch */
.ui-gradient {
background: linear-gradient(in oklch,
hsl(220 70% 50%),
hsl(260 70% 60%)
);
}
/* Prevent hue wrapping artifacts in button gradients */
.button-gradient {
background: linear-gradient(in oklch shorter hue,
var(--primary-light),
var(--primary-dark)
);
}
/* Smooth color transitions for data visualization */
.chart-gradient {
background: linear-gradient(in oklch,
oklch(70% 0.15 200),
oklch(70% 0.15 280)
);
}
Gamut Mapping for Out-of-Gamut Colors
Understanding Out-of-Gamut Colors
When a color is specified in a wide gamut color space but the display cannot render it, the color is said to be "out of gamut." This situation occurs when you specify a highly saturated color using OKLCh or a wide gamut color space like display-p3, but the user's display only supports the standard sRGB color range. CSS Color Module Level 4 defines algorithms for browsers to map these colors into the display's gamut while preserving the intended appearance as much as possible.
Modern displays support varying color gamuts, from standard sRGB to wide gamut P3 and Rec. 2020. When you specify colors in a space larger than the display can render, the browser must decide how to handle the discrepancy. Understanding gamut mapping helps developers create appropriate fallbacks and anticipate how colors might appear on different devices.
Gamut Mapping Strategies
The specification describes several gamut mapping strategies that browsers may employ. While developers typically don't control which algorithm their browser uses, understanding these approaches helps in designing color systems that degrade gracefully:
-
Clip: Simply clips out-of-gamut component values to the valid range, which can shift the hue and saturation. This is the simplest approach but may produce unexpected results for very saturated colors.
-
Clamp: Similar to clip but preserves the general color relationship by scaling values to fit within the gamut while maintaining relative proportions.
-
Desaturate: Reduces saturation until the color fits within the gamut, preserving hue and lightness. This approach maintains the general character of the color while sacrificing vibrancy.
-
Compress: Compresses the color space locally to fit the out-of-gamut color, mapping colors near the edge of the gamut inward to preserve the relative ordering of colors.
/* May be clipped on sRGB displays */
.very-saturated {
color: oklch(70% 0.5 300);
/* Chroma of 0.5 exceeds sRGB gamut on most displays */
}
/* Safer for sRGB fallback - lower chroma */
.more-saturated {
color: oklch(70% 0.25 300);
/* More likely to render consistently across displays */
}
/* Using relative syntax with safe values */
.safe-variant {
color: oklch(from var(--base) l calc(l * 0.8) calc(c * 0.5));
}
Practical Implications
For most web development purposes, browsers handle gamut mapping automatically without developer intervention. However, understanding these concepts helps when designing for wide gamut displays and creating fallbacks for sRGB-only devices. The key is to use progressive enhancement: specify vibrant colors for wide gamut displays while ensuring acceptable appearance on standard displays.
/* Progressive enhancement pattern */
.vibrant-element {
/* Fallback for sRGB displays */
color: oklch(65% 0.15 250);
/* Enhanced for wide gamut displays */
@media (color-gamut: p3) {
color: oklch(65% 0.25 250);
}
}
/* Using color() with fallback */
.wide-gamut-color {
color: rgb(0 100 200); /* sRGB fallback */
color: color(display-p3 0 0.4 0.85); /* Wide gamut enhancement */
}
Browser Support and Progressive Enhancement
Current Browser Support
As of 2025, major browsers have implemented most CSS Color Module Level 4 features. Chrome, Firefox, Safari, and Edge all support the core Level 4 functionality, including modern color syntax, device-independent color spaces, relative color syntax, and the color-mix() function. The specification has reached a mature state where developers can confidently use these features with appropriate fallbacks for older browsers.
Support status for key features:
- Modern syntax (space-separated values, slash alpha): Supported in all major browsers since 2022
- OKLCh and OKLab: Supported in Chrome 111+, Firefox 113+, Safari 15.4+, Edge 111+
- color() function with wide gamut spaces: Supported in Chrome 111+, Firefox 113+, Safari 15.4+
- color-mix(): Supported in Chrome 111+, Firefox 108+, Safari 16.4+
- Relative color syntax: Supported in Chrome 119+, Firefox 113+, Safari 16.4+
- HWB colors: Supported in Chrome 101+, Firefox 111+, Safari 14.1+
Progressive Enhancement Strategy
When implementing Level 4 features, follow a progressive enhancement approach that provides a good experience for all users while enhancing capable browsers. Start with reliable sRGB colors, then layer on modern syntax, device-independent colors, and wide gamut support for supported displays. Our web development team follows these best practices to ensure color techniques work reliably across all browsers.
/* Base layer: Reliable sRGB colors */
.button {
background: #0066cc;
color: white;
}
/* Layer 2: Modern syntax (backward compatible) */
.button {
background: rgb(0 102 204);
color: white;
}
/* Layer 3: Modern syntax with alpha */
.button {
background: rgb(0 102 204 / 0.9);
color: white;
}
/* Layer 4: Wide gamut for supported displays */
@media (color-gamut: p3) {
.button {
background: oklch(60% 0.2 250);
}
}
/* Layer 5: Relative colors with fallback */
.button {
--primary: #0066cc;
background: var(--primary);
}
.button:hover {
background: oklch(from var(--primary) l calc(l + 0.1));
}
Feature Detection
Use @supports and media features to detect browser capabilities and conditionally apply Level 4 features. This allows you to provide enhanced experiences for capable browsers while maintaining compatibility with older ones.
/* Detect modern color syntax support */
@supports (color: oklch(0% 0 0)) {
.wide-gamut {
color: oklch(70% 0.15 280);
}
.color-mix-support {
--primary-muted: color-mix(in oklch, var(--primary), black 60%);
}
}
/* Detect specific color-gamut support */
@media (color-gamut: p3) {
.p3-colors {
color: color(display-p3 0 0.5 1);
}
.enhanced-palette {
--primary: oklch(65% 0.25 250);
--secondary: oklch(60% 0.22 320);
}
}
@media (color-gamut: rec2020) {
.rec2020-colors {
color: color(rec2020 0 0.6 0.9);
}
}
/* Detection for relative color syntax */
@supports (color: oklch(from #fff l c h)) {
.relative-colors {
--light-variant: oklch(from var(--primary) l calc(l + 0.15));
}
}
Migration Guide and Best Practices
Migration Strategy
Migrating to CSS Color Module Level 4 should be a gradual process that doesn't disrupt existing projects. A strategic approach allows you to realize the benefits of modern CSS colors while maintaining backward compatibility and reducing risk.
-
Audit Current Color Usage: Inventory all color declarations in your project, including CSS custom properties, component colors, and design tokens. Identify patterns and opportunities for consolidation.
-
Adopt Modern Syntax Gradually: Start using the space-separated syntax and slash alpha notation for new code while gradually updating existing declarations. The legacy syntax continues to work, so there's no urgency to convert everything at once.
-
Convert to Perceptually Uniform Spaces: For complex color relationships and palettes, consider converting from RGB/HSL to OKLCh for more predictable results. Start with new projects or component libraries before touching established code.
-
Implement Relative Colors for Theming: Replace hard-coded color variations with relative color syntax based on design tokens. This creates more maintainable color systems that automatically adapt when base colors change.
-
Add Wide Gamut Support: Once core migration is complete, add wide gamut colors for supported displays using media queries and feature detection.
Common Pitfalls
-
Converting Colors Automatically: Don't assume a simple mathematical conversion between color spaces will produce visually identical results. RGB to OKLCh conversions may shift perceived colors. Test conversions manually and adjust as needed.
-
Ignoring Contrast: The expanded color palette can lead to accessibility issues. Always verify contrast ratios when using new color spaces, especially with highly saturated colors that may be harder to read.
-
Overusing Wide Gamut: Reserve very saturated colors for emphasis and visual impact. Using them everywhere diminishes their impact and can create overwhelming interfaces.
-
Forgetting Fallbacks: Always provide sRGB fallbacks when using wide gamut colors. Not all users have wide gamut displays, and colors should be acceptable on standard displays.
Recommended Workflow
/* Step 1: Define design tokens in OKLCh for consistency */
:root {
/* Base colors in OKLCh */
--color-primary: oklch(65% 0.2 250);
--color-secondary: oklch(60% 0.18 320);
--color-accent: oklch(70% 0.15 45);
/* Create variations using relative syntax */
--color-primary-light: oklch(from var(--color-primary) l calc(l + 0.15));
--color-primary-dark: oklch(from var(--color-primary) l calc(l - 0.15));
--color-primary-pale: oklch(from var(--color-primary) l 0.95 / 0.15);
/* Use color-mix for specific needs */
--color-primary-muted: color-mix(in oklch, var(--color-primary), black 60%);
--color-secondary-subtle: color-mix(in oklch, var(--color-secondary), white 80%);
}
/* Step 2: Progressive enhancement for wide gamut */
@media (color-gamut: p3) {
:root {
/* More saturated versions for P3 displays */
--color-primary: oklch(65% 0.25 250);
--color-secondary: oklch(60% 0.22 320);
--color-accent: oklch(70% 0.2 45);
}
}
/* Step 3: Modern syntax for all colors */
.button {
background: var(--color-primary);
color: var(--color-primary-foreground);
border: 1px solid var(--color-primary-dark);
}
.button:hover {
background: var(--color-primary-light);
}
/* Step 4: Use color-mix for states and variants */
.card {
background: var(--color-primary-pale);
border-left: 4px solid color-mix(in oklch, var(--color-primary), white 50%);
}
Frequently Asked Questions
What is CSS Color Module Level 4?
CSS Color Module Level 4 is a W3C specification that introduces modern color syntax, device-independent color spaces (OKLCh, Lab, LCH), wide gamut color support (display-p3), relative color syntax, and the color-mix() function for programmatic color manipulation. It represents the most significant update to CSS colors since CSS Color Module Level 3.
Should I use OKLCh or RGB for my colors?
For new projects, OKLCh is recommended because it's perceptually uniform, meaning equal numerical changes produce equal visual changes. This makes color manipulation more predictable and consistent. However, RGB remains valid and can be used where OKLCh support isn't needed or for quick prototyping.
Do I need to replace all my existing RGB colors?
No. The legacy comma-separated syntax remains valid and will continue to work in all browsers. Migration to Level 4 should be gradual, focusing on new code and areas where Level 4 features provide specific benefits like relative colors for theming or wide gamut for vibrant displays.
How do I add wide gamut colors to my site?
Use the color() function with spaces like display-p3, and always provide sRGB fallbacks. You can use @media (color-gamut: p3) to target wide gamut displays specifically. Start with moderate saturation levels and test across devices to ensure acceptable appearance everywhere.
What is the slash notation for alpha?
Level 4 introduces slash notation for alpha transparency that works with any color function: color: rgb(255 0 0 / 0.5) instead of rgba(255, 0, 0, 0.5). This unified approach eliminates the need for separate rgba() and hsla() functions while providing consistent alpha syntax across all color spaces.
How does color-mix() work?
The color-mix() function blends two colors in a specified color space: color-mix(in oklch, #ff0000, #0000ff). The first color's percentage can be adjusted to control the mix ratio, like color-mix(in oklch, #ff0000 30%, #0000ff 70%). Different color spaces produce different mixing results.
What are the browser support considerations?
Modern browsers (Chrome 111+, Firefox 113+, Safari 16.4+, Edge 111+) support most Level 4 features. Use @supports to detect feature availability and @media (color-gamut: p3) for wide gamut detection. Always provide fallbacks for older browsers, particularly for wide gamut colors.
How do I create color palettes with Level 4?
Use OKLCh for perceptual uniformity, then vary lightness and chroma while keeping hue constant. The relative color syntax makes this easy: oklch(from var(--base) l calc(l + 0.15)) creates a lighter variant. Use color-mix() to create tints and shades by mixing with white or black.
Conclusion
CSS Color Module Level 4 represents a transformative update to how developers work with color on the web. The introduction of device-independent color spaces like OKLCh, wide gamut support for modern displays, relative color syntax for dynamic manipulation, and the color-mix() function collectively provide capabilities that were previously only possible with preprocessor functions, JavaScript libraries, or design tools.
These new features address long-standing limitations in CSS color handling. Perceptually uniform color spaces make color manipulation predictable and consistent. Wide gamut support unlocks the full potential of modern displays with vibrant, saturated colors. Relative color syntax enables sophisticated theming systems without JavaScript. And color-mix() provides intuitive programmatic color blending.
As browser support continues to mature, Level 4 colors will become the standard for modern web development. By understanding these new capabilities and adopting them strategically, developers can create more vibrant, accessible, and maintainable color systems for their projects. Partnering with experienced web development services can help you implement these techniques effectively.
The key to successful adoption is progressive enhancement: start with the modern syntax for existing color models, gradually incorporate device-independent colors for new projects, and add wide gamut support where appropriate. With careful attention to accessibility and display compatibility, CSS Color Module Level 4 opens new possibilities for creative and functional color design on the web.
Sources
- W3C CSS Color Module Level 4 - Official W3C specification for CSS color handling
- MDN Web Docs: CSS Colors - Comprehensive developer documentation for CSS color functions
- MDN Web Docs: CSS Color Values - Overview of CSS color spaces and functional notations
- MDN Web Docs: Using Relative Colors - Guide to relative color syntax
- Evil Martians: OKLCH in CSS - Industry perspective on migrating to OKLCH