On this page
Modern CSS can derive colour variations directly from a base value, reducing the need to maintain many disconnected hex codes. Two features do different jobs: color-mix() combines colours, while relative colour syntax starts from one colour and changes its channels. Choosing the right operation makes a palette easier to reason about.
Decide whether you need mixing or channel adjustment
Use the colour converter to inspect a base colour in several representations before deciding what should change. RGB exposes light channels, HSL offers familiar hue and saturation controls, and perceptual spaces such as OKLCH provide a different model for lightness and chroma. Equivalent representations describe the same starting colour, not different palette options.
Choose colour mixing when the requirement describes a blend: a brand tint on a white surface, a subtle brand-neutral border or a translucent wash. Choose relative syntax when the requirement names a channel operation: preserve the hue, lower chroma or set a specific alpha.
Neither feature decides whether the result is suitable for its role. A mathematically related hover colour can still be hard to see, and a pleasant tint can still fail under text. Keep derived values inside a deliberate role system, using the design tokens guide as background for naming those responsibilities.
Start with one real component and one known base. Avoid converting an entire palette into formulas before you have compared the output with the existing design. A formula is valuable when it expresses an intentional relationship, not merely because it removes literal values.
Mix two colours in a chosen space
The basic color-mix() expression specifies an interpolation space and two colours with optional percentages. For an opaque brand tint, mix the brand with a surface colour. For a translucent overlay, mix it with transparent. These outputs serve different purposes even if they look similar on white.
:root {
--brand: #3659bb;
--surface: #ffffff;
--brand-tint: color-mix(in oklab, var(--brand) 12%, var(--surface));
--brand-overlay: color-mix(in srgb, var(--brand) 12%, transparent);
}
.callout { background: var(--brand-tint); }
.selection-wash { background: var(--brand-overlay); }When one percentage is omitted, it is normally inferred as the remainder to 100%. When both are omitted, the weights are equal. More complicated totals have normalisation and alpha consequences, so keep ordinary palette recipes simple. MDN's color-mix() reference explains the full percentage rules.
The interpolation space affects the route between endpoints. Mixing in sRGB is not visually identical to mixing in Oklab. Oklab is often useful for relatively even perceptual transitions, while a cylindrical space such as OKLCH also introduces choices about the hue path. There is no universally best space independent of the intended result.
Do not describe a 12% brand mix as 12% saturation or 12% lightness. It is a contribution to an interpolation operation. Its final appearance depends on both endpoints, the chosen space and, for transparent output, the background beneath it.
Adjust channels with relative colour syntax
Relative syntax converts an origin colour into the requested colour space and exposes its channels for reuse or calculation. In an OKLCH expression, l, c and h refer to the converted origin's lightness, chroma and hue. You can preserve channels while changing only the property you intend.
:root {
--brand: #3659bb;
--brand-lighter: oklch(from var(--brand) calc(l + 0.08) c h);
--brand-quieter: oklch(from var(--brand) l calc(c * 0.65) h);
--brand-wash: oklch(from var(--brand) l c h / 0.12);
}The lightness calculation above adds 0.08 on the zero-to-one channel scale; it does not mean an eight-percent improvement in readability. Channel arithmetic is not a contrast guarantee. Large changes can also produce colours outside a display's gamut, so inspect actual rendering rather than trusting the expression alone.
Review MDN's relative colour guide for channel handling and syntax details. In particular, the numeric channel conventions used in calculations can differ from the way authors commonly write absolute colour values. Copying an HSL calculation into OKLCH without checking units and ranges is unsafe.
Use explicit relative adjustments sparingly. If several derived colours need individual corrections to look right, a curated scale may express the design more honestly than a complex chain of formulas. Keep exceptional semantic colours such as warning and danger independent of the brand origin.
Provide fallbacks at the feature boundary
Ship a stable literal value first, then override it when the required syntax is supported. This makes the intended baseline visible and prevents an unsupported feature from becoming the only definition of an important colour.
:root {
--brand: #3659bb;
--brand-tint: #e7ebf7;
--brand-hover: #294698;
}
@supports (color: color-mix(in oklab, red, white)) {
:root {
--brand-tint: color-mix(in oklab, var(--brand) 12%, white);
}
}
@supports (color: oklch(from red l c h)) {
:root {
--brand-hover: oklch(from var(--brand) calc(l - 0.06) c h);
}
}Use a real colour property in feature queries, not a custom property declaration. Custom properties accept broad token sequences, so testing whether one accepts unfamiliar colour text does not establish that the browser can use it as a colour. The explicit queries above test the relevant parsing capability.
Check support against your actual browser policy and embedded webviews. Treat mixing and relative syntax as separate capabilities rather than assuming they arrived together everywhere. MDN's @supports documentation describes how to structure these conditional enhancements.
Validate the resulting relationships
Review each output in its final component and theme. A tint mixed with white is tied to that surface assumption; it may look wrong on a dark page. A transparent wash changes with the substrate beneath it. Decide which behaviour is intended instead of swapping between the two casually.
Measure text and background combinations with the contrast checker. Include hover and selected states, not only the original brand pair. Relative lightness changes do not map directly onto WCAG contrast ratios, and a derivation that works for one brand hue may fail when a customer supplies another.
Keep formulas close to their semantic role definitions. Document the origin, operation and expected background so maintainers can understand why a result exists. When supporting user-supplied colours, validate inputs and supply approved fallback pairs rather than assuming any generated palette will remain readable.
Key takeaways
- Use mixing for blends and relative syntax for targeted channel changes.
- Choose interpolation spaces intentionally and compare the actual output.
- Separate opaque tints from transparent overlays.
- Test each feature independently and retain useful literal fallbacks.
- Check every derived colour in its real foreground-background relationship.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor