Every interactive state in our design system came with extra structure.
In code, components carried overlay elements for hover and pressed states. In Figma, designers reproduced those overlays with absolute-positioned layers.
The system avoided creating a new colour for every state, but it made that efficiency expensive to use. Developers debugged extra DOM, designers rebuilt the same layer stack, and missing an overlay meant design and production drifted apart.
The solution
We introduced state tokens built with CSS color-mix(). Each token mixed:
- The enabled semantic colour, such as base.color.selectable.primary.primary-enabled - A light or dark neutral base - The opacity assigned to the interaction state
.button:hover {
background: color-mix(
in srgb,
var(--selectable-primary-enabled),
var(--neutral-state-base) 8%
);
}This produced the final state colour in one declaration. Components no longer needed overlay elements or a matching stack of absolute layers in Figma.
We grouped selectable tokens into primary, on-surface, and destructive variants, each with a predictable namespace under base.color.selectable. That removed guesswork without adding hardcoded colours.
We also prefixed the old tokens with deprecated so teams could migrate without breaking existing components.
Browser support
At the time of implementation, we worked with engineering to validate the browsers Visibuild supported and define fallback behaviour. color-mix() has been broadly available across modern browsers since May 2023, but the interpolation space and supported-browser policy still need to be explicit.
The important architectural choice was not merely using a newer function. It was deciding whether these values would be resolved in the browser, compiled during the token pipeline, or paired with a fallback declaration.
The impact
This update migrated state styling across more than 300 component instances in the design system. In the coded examples we audited, it removed an average of three overlay elements per component.
The verified benefits were:
- Simpler markup - Fewer stacking and pointer-event edge cases - One semantic state token per treatment - Closer alignment between Figma and code - Less documentation and onboarding overhead
Removing DOM layers may reduce rendering work, but we did not capture a performance comparison, so maintainability was the outcome we could substantiate.
Documentation and accessibility
We updated the design-system documentation with before-and-after examples and created a Figma reference where designers could inspect each state in context.
We tested rendered component states rather than treating isolated colour values as inherently accessible. The checks covered text contrast, non-text contrast where a visible boundary or state indicator was required, and whether hover and pressed treatments preserved legibility.
The exact component and background combination still mattered. A token could not carry a blanket accessibility guarantee outside the context where it was rendered.
What I learned
The old system forced Figma and the DOM to share the same structure. The new one let them share the same semantic intent.
That distinction mattered more than the specific CSS function. Design and code can use different mechanics when the token contract makes the state, purpose, and expected result unambiguous.