Common CSS Color Variables and Properties in Theming

  • report
    Disclaimer
    Click for Disclaimer
    This Post is over a year old (first published about 3 years ago). As such, please keep in mind that some of the information may no longer be accurate, best practice, or a reflection of how I would approach the same thing today.
  • infoFull Post Details
    info_outlineClick for Full Post Details
    Date Posted:
    Jan. 06, 2021
    Last Updated:
    Jan. 03, 2021
  • classTags
    classClick for Tags

Table of Contents

Intro

As I’ve dipped in and out of different front-end theming frameworks, something that I have noticed is a lack of standardization when it comes to global theme color variables.

For the unfamiliar, many front-end frameworks provide global (e.i. shared, or root) color variables that can be easily re-used across your app, and often support customization and hot-swapping of values for instantaneous theme switching (e.g. between “dark mode” and “light mode”).

This blog post is the result of a small research and classification attempt by myself, to see if I can identify some common trends and tools that might be useful within this area of theme development.

Collected Framework Results

Examples of Custom Variable Sets

As noted in the intro, the whole reason behind this post was because I noticed how many different combinations of global CSS variables I was seeing. In addition to noting the variance between design frameworks (see above), I also wanted to callout some interesting examples of highly customized sets of global CSS variables.

Twitch (twitch.tv)

Twitch has, literally, hundreds of :root variables, some of which are pretty standard, such as --color-accent, while others are pretty specific to their use-case, such as --color-fill-channel-status-text-indicator-offline.

There is a built-in theme switcher, to switch between dark and light. Switching between them swaps a class in the top level element (e.g. tw-root--theme-light), which in turn changes which values are assigned to the root-level variables. This is a common CSS-variable-based theming approach.

StackOverflow (stackoverflow.com)

StackOverflow makes use of a lot of patterns covered in this post, as well as some advanced patterns that shift more theme processing towards native CSS and away from pre-processors.

For example, they define the primary color in separate chunks (both HSL and RGB), and then dynamically compute derived colors by combining those values and optionally applying calculations. Here is how they compute a derived color, which is a shade of the primary color darkened by 30%:

:root {
    --theme-primary-color-h: 26.53846154;
    --theme-primary-color-s: 90.43478261%;
    --theme-primary-color-l: 54.90196078%;
    --theme-primary-color-darken-30: hsl(var(--theme-primary-color-h), var(--theme-primary-color-s), calc(var(--theme-primary-color-l) - 30%));
}

Some Common Patterns

Here is a breakdown of some common recurring patterns I saw:

Color Wheel based names

Although the values people pick might not always correspond with it, these variable names seem to be based on the standard color wheel.

  • Examples:
    • --primary: red
    • --secondary: orange
    • --tertiary: orangered

Named scaled color palettes / swatches

Since there is no such thing as just “red“, many themes maintain their own palette of colors, with different weights / intensities:

  • Examples:
    • --orange-400: #f9872c;
    • --orange-500: #f4740d;
    • --yellow-400: #d8ba45;
  • Notes:
    • This does not appear to be standardized either. Many sites and frameworks use a scale that seems to correlate with font weights (100-900), where others might use 0-10, or -a# for properties using alpha values.
    • I think part of the reason why this is so common is because creating “shades” in CSS is a lot more complicated than in SASS (where you have access to native functions like lighten() and darken()). Another consideration is performance; having these values pre-populated is probably has significantly better performance than a bunch of calls to calc(), especially if you have dozens of colors and derivative shades / variations.

Contrast Variations

When specifying a color, you have to consider all the different ways it will be used, and that includes how it contrasts with the colors around it. Thus, thus some systems like to explicitly declare contrasting color values.

Often, the contrast value is used for text or icons; for example, the Material Design system has an entire section devoted to what they call “On Colors” – this is a color that should be used for text or iconography when it appears “on” another surface.

:root {
    --primary: #261ff2;
    --secondary: #ea80fc;
    --on-primary: #fafafa;
    --on-secondary: #000504;
}

The main concern with these values should be of proper contrast and accessibility; you don’t want yellow text on a yellow background.

In some systems, these contrasting colors might be automatically derived based on calculated minimums (e.g. WCAG / WebAIM)

You can also see these variables reflected in Material’s custom theme color docs. And the Ionic Framework places particular importance on contrast in color variables

Semantic Named Colors

Although some might call primary and secondary semantic variables, I think the best examples of what one might classify as semantic color variables are things like:

:root {
    --success: #28a745;
    --info: #17a2b8;
    --warning: #ffc107;
    --danger: #dc3545;
}

These come directly from Bootstrap 4.1’s root variables

For those unfamiliar, the term semantic, within the context of variable naming, usually means that the name itself conveys the meaning and/or application of the value held, rather then relying on said value.

The benefit to these types of values is largely in readability and ease of refactoring. For example, imagine that you have a bunch of components that show errors. You might improve refactor-ability and ease of reading by changing:

background-color: var(--red-600);

to

background-color: var(--danger);

Brand Colors

Although primary and secondary colors usually are the main brand colors, a brand will often have a large palette that makes up their branding guidelines, and you might see this reflected in CSS variables.

For example, if you open up a new empty Chrome in tab, you are likely to see variables like --google-blue-refresh-700. Or, on twitch.tv, you might see a smattering of --color-amazon throughout the site, as well as things like --color-brand-accent-cherry.

If you have a really massive application, and especially if it has third party assets or different themed areas, it might be helpful to clearly differentiate between variables that are part of the application theme, vs part of your actual overall brand (and stricter brand guidelines).

Color Parts / Calc Helpers

I’m not trying to diss CSS (I love CSS!), but the truth is that there are not a whole lot of built-in functions for manipulating hex based color values (especially compared with things like SASS).

One really neat trick to work around this fact is to store color values as their individual parts (either R-G-B or H-S-L), and then put them together and manipulate them in the process.

Here is an example of this in action:

:root {
    --theme-primary-color-h: 242deg;
    --theme-primary-color-s: 56%;
    --theme-primary-color-l: 62%;
    --theme-primary-color-darken-30: hsl(var(--theme-primary-color-h), var(--theme-primary-color-s), calc(var(--theme-primary-color-l) - 30%));
}

Note: The hue value in CSS can be written as either a number (242) with deg unit implied, or with angle unit (e.g. 242deg). See docs for details.

This writeup by Una is amazing guide to using this approach to dynamically generate derived colors from your CSS variables.

Before you dismiss this approach, I’ll note that several high-volume production sites (such as StackOverflow) use this technique in CSS

Differentiating Variable Types

You might notice that some applications / sites are very careful to distinguish color properties, and they will always use things like --border-color: instead of just --border:.

A good reason to do so is if your app stores (or could in the future) non color values in CSS custom properties. Not everyone always remembers this, but CSS custom properties can actually store all types of values beyond just colors; you can store strings, numbers, or any CSS value.

Thus, it might be wise to differentiate color values from the start, since that will make refactoring and readability easier (“Is --border for border color or border value, or border px?”).

Summary

Although I enjoy consistency and standardization, I think seeing a moderate level of variance in how CSS custom properties are getting used and their naming conventions is actually a good thing. It speaks to the flexibility of CSS and what I believe is important part of web development, which is to use what works best for the situation and your users, not necessarily what the most popular framework dictates.

More Resources

Cover image background – Photo by Keila Hötzel on Unsplash

Leave a Reply

Your email address will not be published.