Theming Design Systems
April 3, 2024
Working on a design system for a single brand is challenging enough. Eventually, someone will want the design system to support a new product or vertical that deviates from the company's brand.
Rather than duplicating a bunch of CSS classes to support this effort, we can use CSS custom properties to override our system's default choices.
First, a controversial opinion(?)
I've seen a lot of systems that expose all of their design tokens as CSS custom properties so that developers have the freedom to access any of the system's values when they're styling new features.
The goal of Pit Viper, Turquoise Health's design system, is that developers never write CSS. In order to achieve that, all of the design tokens are stored as Sass variables and used in various component and utility classes to support any context developers might need to apply them in.
One reason for having a design system in the first place is to establish how, when and why the system's values are used in different contexts. Giving developers access to ALL of those values lets them potentially apply them in contexts where they weren't intended to be used. At that point, you might as well just make Tailwind your design system and call it a day.
Por qué no los dos?
We can combine CSS custom properties and Sass variables to create a powerful theming system that allows us to override our system's default values.
For example, let's take a look at a color utility called .pv-surface-brand
.
.pv-surface-brand {
background-color: var(--color-background-brand, #{$color-background-brand});
}
We're using the var()
function to set the background color to the custom property, --color-background-brand
and, if it's not set, it will fall back to the value of the token $color-background-brand
from our Sass tokens. This will compile to:
.pv-surface-brand {
background-color: var(--color-background-brand, #02363D);
}
We use this pattern throughout Pit Viper to allow theming of colors, fonts and other values while retaining the default styling of everything else. Any projects that use Pit Viper can create a separate CSS file with their theme and override any values that support theming.
Did you know?
For our documentation we came up with a fun way of demonstrating the theme system. Did you know that you can use the style
attribute on a <style>
tag? And did you know that you can set display: block
on the style tag to make its contents appear on the page? And did you also know you can then add the contenteditable
attribute to edit it right on the page? And did you further know that George Bailey is dancing right over that crack? And I've got the key. Sorry, what was I talking about?
Oh yeah, if we put all that together, we can apply the variables on the page and allow the user to update them in realtime without any JavaScript! Go ahead, try it! Edit any of the values in the code block below to change them on this page. You can refresh the page to reset the styles to the default.
Theming SVGs
We can scope our custom theme to the :root
like in the example above, but we can also scope it to a class. And if we use the same fallback pattern with the var()
function inside an inline SVG, we can apply our theme to it as well.
The Pit Viper logo below uses the same custom properties as above, but they're scoped to a container with the class myTheme
. You can update these values below, or delete the entire code block and the colors will inherit from the :root
example above.
Wrapping up
You can see how this could be applied in a bunch of use cases, from doing a promotional holiday theme on your website to creating a white label website where users can customize their color scheme to match their brand without going full MySpace on your design system.
Special thanks to Andy Carolan who designed the amazing Pit Viper logo that everyone at Turquoise Health loves.