CSS Interaction Theming with Accent Color and Color Contrast
Cory Rylan
- 4 minutes
Theming in CSS has improved quite a bit in the past few years. CSS Custom Properties introduced true dynamic theming to the Web. New features recently have shipped or are being proposed that can take theming even further.
Features such as color-scheme
allow us or the user to define preferences such as dark themes or high contrast modes in the browser. This immediately improves the default native controls when switching to color-scheme: dark
.
The accent-color property makes color customizations across native controls easy and with only one line of CSS. The benefit of accent-color
is the browser can automatically generate the various color states and contrasts based on the single color provided. This ensures a consistent and accessible experience for users and reduces a lot of custom CSS from having to be created.
Now while accent-color
and color-scheme
helps customize native controls easier, it doesn't help us with custom components or elements we want to customize, like buttons.
Typically, when styling interaction states on buttons, we adjust the element's background color based on the state.
button {
background: gray;
}
button:hover {
background: lightgray;
}
button:hover {
background: darkgray;
}
Reassigning the background colors, however, introduces a lot of maintenance. In addition, for each color of an interactive element, we have several potential interaction states, default
, hover
, active
, selected
, and disabled
. Then if we add theming, this further complicates the number of color values we have to define.
Secondary Background Color with CSS Background Image
Leveraging the CSS background-image property, we can create a "secondary" background color that we can adjust based on the current state.
button {
background: blue;
background-image: linear-gradient(hsla(0, 0%, 0%, 0.5) 0 0);
}
Using HSLA, we can adjust the alpha value of either black to darken or white to lighten the background color. The benefit to this over CSS filter
/brighten
is that it only changes the background color and preserves the original text color.
button:hover {
background-image: linear-gradient(hsla(0, 0%, 0%, 0.1) 0 0);
}
button:active {
background-image: linear-gradient(hsla(0, 100%, 100%, 0.1) 0 0);
}
With the CSS background-image
we can easily adjust the "interaction" layer without redefining new colors. We can abstract this into a more generic solution via CSS Custom Properties.
:root {
--interaction: 0;
--interaction-hover: 0.1;
--interaction-active: 0.1;
}
[interaction] {
background-image: linear-gradient(hsla(0, 0%, 0%, var(--interaction)) 0 0);
}
[interaction]:hover {
background-image: linear-gradient(hsla(0, 0%, 0%, var(--interaction-hover)) 0 0);
}
[interaction]:active {
background-image: linear-gradient(hsla(0, 100%, 100%, var(--interaction-active)) 0 0);
}
Now we can adjust our accent color and alpha value with CSS custom properties and automatically create new visual interaction states.
CSS Color Contrast
We can dynamically change the background color now with our interactions; however, this only partially fixes some of the issues. For example, if our background color is too light, we may lose the contrast needed for our text.
Notice in Chrome, the checkmark is now dark relative to the background green of our checkbox. When using the CSS accent-color
the browser will automatically switch the text/foreground color to the appropriate contrast. However, for our button, the contrast is broken. Unfortunately, there has yet to be an easy cross-browser solution for this. Luckily there is a new CSS spec, color-contrast which can provide this automatic contrast checking.
button {
color: color-contrast(#5fca49 vs #fff, #000);
}
The color-contrast
function takes a base color and a list of color values to compare. The browser will determine which color provides the appropriate contrast for the background and assign that color. This feature gives us the native functionality that browsers internally have today.
With Safari Technical Preview, you can enable CSS color-contrast
behind the experimental flag.
Now with any accent color, we choose the text color that will automatically adjust to the best contrast level.
With accent-color
and hopefully the adoption of color-contrast
automatic and configurable interactions, styles are making themeing in CSS easier than ever. Hopefully, future API proposals will enable us to leverage the internals of the accent-color
feature for custom elements without the need for workarounds like background-image
. Check out the full demo below!