CSS Custom Properties and Theming
We posted not long ago about the difference between native CSS variables (custom properties) and preprocessor variables. There are a few esoteric things preprocessor variables can do that native variables cannot, but for the most part, native variables can do the same things. But, they are more powerful because of how they are live-interpolated. Should their values ever change (e.g. JavaScript, media query hits, etc) the change triggers immediate change on the site. Cool, right? But still, how actually useful is that? What are the major use cases? I think we’re still seeing those shake out. One use case, it occurred to me, would be theming of a site (think: custom colors for elements around a site). Rather than writing different CSS for a bunch of different themes, or writing JavaScript that targets all the elements we intend to change and changing them), we just write one base set of CSS that utilizes variables and set those variables to the theme colors. Imagine we allow the header and footer background of our site to be customized.
Maybe there is a subheader with a darker variation of that color. Here’s a little trick to lay a transparent layer of color over another:
Where does —mainColor come from? With theming, the idea is that you ask the user for it. Fortunately we have color inputs:
And you could store that value in a database or any other storage mechanism you want. Here’s a little demo where the value is stored in localStorage:
The value is plucked out of localStorage and used when the page loads. A default value is also set (in CSS), in case that doesn’t exist. What makes the above demo so compelling, to me, is how little code it is. Maintaining this as a a feature on a site is largely a CSS endeavour and seems flexible enough to stand the test of time (probably). Not unusually, I was way behind on this one. Lots of people think of theming as one of the major use-cases for CSS Custom Properties. Let’s look at some other folks examples.
Giacomo Zinetti has the same kind of color-picker implementation
Examples and advice from Harry Roberts
He wrote “Pragmatic, Practical, and Progressive Theming with Custom Properties”, in which he pointed to apps like Twitter and Trello that offer theming directly to users: Harry does a lot of consulting, and to my surprise, finds himself working with companies that want to do this a lot. He warns:
Theming, the vast majority of the time, is a complete nice-to-have. It is not business critical or usually even important. If you are asked to provide such theming, do not do so at the expense of performance or code quality.
In a real-world application of theming through Custom Properties, Dan Bahrami recounts how they did it on Geckoboard, the product he works on: It’s a React product, but they aren’t using any styles-in-JavaScript stuff, so they opted to do the theming with Custom Properties, through Sass.
@mixin variable($property, $variable, $fallback) < #: $fallback; #: var($variable); >
They also created react-custom-properties which all about applying Custom Properties to components, taking advantage of the fact that you can set Custom Properties as inline styles:
More than one color and property
It’s not only colors that can change, a Custom Property can be any valid value. Here’s Keith Clark with a demo with multiple colors as well as font size:
And David Darnes with theming built into a Jekyll site: Which uses color modifiers within color functions themselves:
Which is not supported in all browsers. Greg also pointed out that CSS4 color functions (which we’ve covered before), will make all this theming stuff even more powerful.
The Polymer Project themes through Custom Properties
iron-icon < fill: var(--icon-toggle-color, rgba(0,0,0,0)); stroke: var(--icon-toggle-outline-color, currentcolor); >:host([pressed]) iron-icon
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Mobile / Tablet
Opera Mini and IE are notably missing. We already covered the idea of a fallback through setting a valid non-variable property before the one using a Custom Property. Like many modern CSS features, you can use @supports to test for support before using:
It always depends on the situation, but just putting fallbacks on a previous declaration is probably the most useful way to deal with non-support in CSS. There are also edge cases. Michael Scharnagl documents a JavaScript method for testing:
if (window.CSS && window.CSS.supports && window.CSS.supports('--a', 0)) < // CSS custom properties supported. >else < // CSS custom properties not supported >
When setting colors for text, and the color behind that text, the contrast between those colors is an accessibility issue. Too little contrast, too hard to read. One somewhat common solution to this is to choose whether the text should be light or dark (white or black) based on the color behind it. David Halford has a demo calculating this with JavaScript:
A guide to theming in CSS
Over the years, CSS has evolved to cater to the ever-growing needs and flexibility requirements of modern UI design. The demand for highly customizable applications is rapidly increasing, and with that the need for developers who can create such websites.
Theming is the ability to style various aspects of our website differently depending on a context, while still maintaining visual appeal. It could be as simple as changing colors and backgrounds or font-sizes and icons. In CSS, we can achieve theming by piecing together various CSS variables (props) in a context (e.g, black and white) to enable better presentation of an application.
In this tutorial, we’ll cover how to develop apps that are theme-aware using CSS variables. We’ll also learn how to use CSS variables and JavaScript to customize websites and applications.
CSS Variables
A CSS variable (also called a custom property) is a variable you assign a CSS value to and reuse throughout your application.
In older versions of CSS, it’s common to define different values for CSS properties and apply them repeatedly whereever they are needed. In the example below, the a element is set to black (#000), and although other elements (p) will also need to use the same color value (#000), you need to explicitly type out the color value (#000) every time you want to use it on another element.
With CSS variables, we only need to define the value once and then reference it wherever needed. It is identical to defining a variable in any programming language. In the example below, we declare a variable black at the top level of our CSS document and then reference it wherever it is needed.
N/B: When declaring CSS variables, the syntax is **—** , followed by the name of the variable. To reference it, we call var(—variable-name) on the property we wish to assign it to.
Inheritance
CSS variables can be scoped to certain components in an application and can be overridden in inner components when necessary.
This is one feature that makes CSS variables stand out, and it’s also what makes it perfect for creating theme0aware websites. In the example below, the inner div and outer div have been defined with two different background colors. First, we define —some-color at .outer-div by setting its value to black. Then we redefine it at .inner-div by setting it’s value to white, causing it to override its value under this element.
The first .text-color gets the value that was defined at .outer-div because that is its parent, while the second .text-color div gets it from .inner-div as that is the direct child of that div.
Switching themes
With the illustrations above, all that’s left to make a theme-able website is to figure out a way to swap the property values of various elements based on the current context.
Over 200k developers use LogRocket to create better digital experiences
Learn more →
To better understand the concept, let’s talk about black and white themes. At a very basic level, to support black and white themes on a website, the background and text color will have to alternate to maintain readability. So, when we are viewing the black theme, the text color should be white, and when we are viewing the white theme, the text color should be black.
The following pen shows what the website looks like on a white theme:
The following pen shows what the website looks like on a black theme:
Typically, on a website, we may have a toggle that enables us to switch between the available themes. To change the CSS variables in real-time, we need to add a little bit of JavaScript to control the process.
Let’s create a page with dark and light themes and add some JavaScript so that it switches whenever the checkbox is clicked:
In the CSS above, we define two site-wide color sets:
The root contains the default colors needed for the light theme, and “data-theme=dark” contains all the colors needed for a dark theme on various parts of our website.
We’ve also added a toggle box to be used to swap out themes whenever a user clicks.
Next, we add some JavaScript to make the swap possible:
const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); function switchTheme(e) < if (e.target.checked) < document.documentElement.setAttribute('data-theme', 'dark'); >else < document.documentElement.setAttribute('data-theme', 'light'); >> toggleSwitch.addEventListener('change', switchTheme, false);
Here’s how we achieved this:
- We select the checkbox with the query selector
- We create a function to change the context of the application to dark when checked or light when unchecked
- Finally, we add an event listener to listen for the actions
A good thing to have on a themeable website is the ability to store the user preference, so the next time the user visits the website it automatically loads up in that context. To achieve that, we will make use of local storage.
Add the following lines to the snippet above:
Now that we are saving the user preference, we need to check for it every time a site loads so we know which version to display to the user. Add this snippet at the top level in the Javascript document:
const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null; if (currentTheme) < document.documentElement.setAttribute('data-theme', currentTheme); if (currentTheme === 'dark') < toggleSwitch.checked = true; >>
Here, we check if a theme preference has been stored in local storage. Based on the value stored, we toggle the box (by setting it to true or false) to swap the needed variables.
Conclusion
In this tutorial, we learned about theming and how to create a basic version of a theme-able website using CSS variables. We also learned about variable inheritance and local scoping alongside how to make it all come together using basic JavaScript. To learn more about CSS theming check out the official docs here.
Is your frontend hogging your users’ CPU?
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — Start monitoring for free.