CSS Variables: How we use them for theming at FloQast

Dec 21, 2020 | By Roy Tinker

Picture of paint colors and brushes

In a previous post, J.C. talked about how we're migrating to decoupled micro-frontends here at FloQast. We're also working on a design and theme refresh 🎉 🎨, which affects several micro-frontends, the hub, and an internal UI library.

As we've worked, we've found the same sets of color variables in multiple places – in JavaScript files (for interpolating colors into Styled Components declarations), SCSS files, and across several front-end projects, all of which are composed into a single-page application at run time.

How can we DRY up these color declarations while making them as easy as possible to consume and change?

CSS Variables

The best solution we've found is CSS variables, also known as custom properties. In case you're unfamiliar, they're normally declared like this:

:root { 
  --primary: rgb(14, 51, 98);
  --primary-active: rgb(14, 51, 98, 0.38); 
}

... and consumed like this:

.some-button { 
  background-color: var(--primary); 
} 

.some-button:active { 
  background-color: var(--primary-active); 
}

To make the app's theme dynamic, make sure all colors used in the app are declared and consumed as CSS variables.

Note: If you're using Styled Components, you'll want to wrap the top-level declaration in a call to createGlobalStyle.

Declarations and overrides

You will need to declare and add these variables to each HTML page only once. Because we're using a micro-frontend architecture, the client hub is where our global variables are declared and added to the HTML document – the Single Source of Truth.

The client hub could also also handle app-wide theme changes, such as dark mode or user-configurable themes, just by applying different values to the color variables.

With CSS, you can override any property at any level for any element or subtree of elements—including CSS variables. This means any micro-frontend can override theme colors for its own area of the app.

Assuming a micro-client's markup is in a particular div:

<div class="settings-client"> 
  <!-- settings client markup goes here --> 
</div>

we can override styles for that client only:

.settings-client { 
  --primary: rgb(255, 6, 185); 
  --primary-active: rgb(255, 6, 185, 0.4); 
}

If you're using Styled Components and React (which we use and recommend), the above markup and styles might look like this:

const SettingsClientWrapper = styled.div` 
  --primary: rgb(255, 6, 185); 
  --primary-active: rgb(255, 6, 185, 0.4); 
`;

// elsewhere, in your JSX markup

return ( 
  //... 
  <SettingsClientWrapper> 
    {/* settings client markup goes here */} 
  </SettingsClientWrapper> 
  //... 
);

DRYing up RGB values

You may have noticed that our :root colors above repeat the r/g/b color components. Most theme systems have sets of colors that derive from each other. Can we DRY these up?

It turns out we can:

:root { 
  /* arbitrary strings work as variables! */ 
  --primary-base: 14, 51, 98; 
  --primary: rgb(var(--primary-base)); 
  --primary-active: rgb(var(--primary-base), 0.38); 
}

There are limitations to how dynamic we can be. var(...) always inserts spaces before and after the value it interpolates, so expressions like #var(--red-value)218F or var(--width)px will throw syntax errors. (See the spec for more details.)

Derived colors

All theme colors are usually derived from a few base colors. These derivations have to be done manually, on the server, or in JavaScript.

As of yet, CSS isn't capable of color operations beyond string interpolation (see above) or calc() expressions (see Example 11 in the var() section of the spec).

 

When browser vendors implement CSS Color Module Level 5, we'll be able to derive the theme from a few base colors using CSS functions like color-adjust(), color-mix(), and color-contrast().

:root { 
  /* somewhere, over the rainbow ... */ 
  --primary: rgb(255, 6, 185); 
  --primary-active: color-mix(var(--primary), transparent, 40%); 
}

Until then...

Whether you derive your theme colors manually or in code, you can keep them DRY by:

  • Declaring theme colors in a single location.
  • Overriding colors for specific clients or sub-trees using the CSS cascade.
  • Referencing and/or interpolating other variables where possible.
Roy Tinker
Roy Tinker
Roy is a Senior Software Engineer at FloQast. He enjoys building user interfaces that delight people and help them do their work. When he's not writing and maintaining code, he enjoys spending time with his family, reading, and grooving on the piano.

Check out research, videos, case studies, and more!

Learn more about working at FloQast!