How To Configure Application Color Schemes With CSS Custom Properties
Variables are a basic tool that help organize colors on a project. For a long time, front-end engineers used preprocessor variables to configure colors on a project. But now, many developers prefer the modern native mechanism for organizing color variables: CSS Custom Properties. Their most important advantage over preprocessor variables is that they work in realtime, not at the compilation stage of the project, and have support for the cascade model which allows you to use inheritance and redefinition of values on the fly.
When you’re trying to organize an application color scheme, you can always place all custom properties that relate to color in the root section, name them, and use it in all needed places.
I now have three main colors. On the basis of these, I will calculate the tones and mid-tones (the HSL format in combination with the
calc function is a very useful tool for this). By changing the lightness value, I can generate several additional colors for the palette.
Use Component Colors
Large web projects always contain decomposition; we split everything into small components and reuse them in many places. Each component usually has its own style meaning it doesn’t matter what we used to decompose BEM or CSS Modules, or another approach; it’s important that each such piece of code can be called local scope and reused.
In general, I see the point in using color variables at the component level in two cases.
The first is when components that according to application style guide are repeated with different settings, e.g. buttons for different needs like primary (brand) button, secondary button, tertiary, and so on.
The second is when components that have several states with different colors, e.g. button hover, active and focus states; normal and invalid states for input or select field, and so on.
A more rare case when component variables may come in handy is the functionality of a "white label". The "white label" is a service feature that allows the user to customize or brand some part of the user interface to improve the experience of interacting with their clients. For example, electronic documents that a user shares with his customers through a service or email templates. In this case, the variables at the component level will help to configure certain components separately from the rest of the color theme of the application.
In the example below, I’ve now added controls for customizing colors of the primary (brand) button. Using color variables of the component level we can configure UI controls separately from each other.
What about green? We can clearly define it as the primary or brand color, most likely, if the color of the main button changes, then the color of the link and header of the first level will also change.
What about red? Invalid state of input fields, error messages, and the destructive buttons will have the same color at the whole application level. This is a pattern. Now I can define several common functional variables in the root section:
Now I understand that this was not the right way. Firstly, if you put component colors into the root section, then you break the separation of concerns principle. As a result, you can end up with redundant CSS in the stylesheet. For example, you have the folder of components where each component has its own styles. You also have a common stylesheet where you describe color variables in the root section. You decide to remove the button component; in this case, you must remember to also remove the variables associated with the button from the common styles file.
Secondly, this is not the best solution in terms of performance. Yes, a color change causes only the process of a repaint, not reflow/layout, this in itself is not too costly, but when you make some changes at the highest level, you will use more resources to check the entire tree than when these changes are in a small local area. I recommend reading the performance benchmark of CSS variables from Lisi Linhart for more details.
On my current project Tispr, the team and I use split and do not dump everything in the root, on the high level only a palette and functional colors. Also, we are not afraid of IE11, because this problem is solved by the corresponding polyfill. Just install npm module ie11-custom-properties and import library into your application JS bundle:
// Use ES6 syntax import "ie11-custom-properties"; // or CommonJS require('ie11-custom-properties');
Or add module by script tag:
<script async src="./node_modules/ie11-custom-properties/ie11CustomProperties.js">
Also, you can add the library without npm via CDN. The work of this polyfill is based on the fact that IE11 has minimal support for custom properties, where properties can be defined and read based on the cascade. This is not possible with properties starting with double dashes, but possibly with a single dash (the mechanism similar to vendor prefixes). You can read more about this in the repository documentation, as well as get acquainted with some limits. Other browsers will ignore this polyfill.
Below is a palette of the Tispr web application as well as the controls of the “white label” functionality for the e-documents (such as user contracts, invoices, or proposals).
Splitting colors into three levels (palette, functional, and component) can help you be more adaptive to changes and new requirements while working on a project. I believe that CSS Custom Properties are the right tool for organizing color split — it does not matter what you use for styling: pure CSS, preprocessors, or CSS-in-JS approach.
I came to this approach through my own experience, but I’m not alone. Sara Soueidan described in her article a similar approach in which she split variables into global and component levels.
I would also like to suggest reading the Lea Verou’s article where she describes possible cases of applying CSS variables (not only in terms of color).