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
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.
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.
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,
disabled. Then if we add theming, this further complicates the number of color values we have to define.
Leveraging the CSS background-image property, we can create a "secondary" background color that we can adjust based on the current state.
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
brighten is that it only changes the background color and preserves the original text color.
background-image: linear-gradient(hsla(0, 0%, 0%, 0.1) 0 0);
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.
background-image: linear-gradient(hsla(0, 0%, 0%, var(--interaction)) 0 0);
background-image: linear-gradient(hsla(0, 0%, 0%, var(--interaction-hover)) 0 0);
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.
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.
color: color-contrast(#5fca49 vs #fff, #000);
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.
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!