CSS Theme Organization Best Practices

The three levels of theme abstraction that would make your CSS themes invincible.

tl;dr- Skip to the sandboxes of examples of theming best practices.

Themes are great and we, in Welldone Software, strongly suggest using them. They are great because:

The website’s design is controlled from a single point, without programming.

The website’s design can be changed dynamically without a page refresh.

Create style skins like a dark mode.

Conveniently run an A-B test of how users react to different designs.

Change your website’s design entirely, or only a certain part of it.

Makes the Implementation of design updates and even re-branding much easier.

There are many tools and ways to create themes for your application in different frameworks and environments. For example:

CSS custom properties (the one that we will use in the article)

styled-components theming (an example of how to do it using styled-components is found at the end of the article)

material-theming

The article is valid for implementations of themes of any kind. An example using styled-components would be present in the end of this article to demonstrate it.

I’ve seen how teams utilize them successfully. From my experience, however, most of the initial implementations of themes are terrible! Themes usually become robust only after a massive re-write once they are actually needed in changing the application’s appearance.

I’m here to help you write your themes in a powerful fashion from the very first implementation of your design.

First Abstraction — Colors

Let’s look at the following code. What’s wrong with it?

The answer is that in order to change the button’s background you would have to change it in .action-button instead of changing it using the theme in html.colors .

By the way, notice how in the examples to follow I’m not using :root for theming with CSS custom properties. I’m doing this because it allows me to break the theme into different parts and because it doesn’t have any inherent advantages over using the html tag for this purpose.

Second Abstraction — Entities

Here’s a better way to do it:

Let’s compare the two ways:

In the first one you miss the power to change the button’s background color from the theme itself by changing the variable --action-button-background-color .

For example, the following file would allow adding the classes dark-mode-colors and dark-mode-entities to the html tag to change the button’s background color, without ever touching the .action-button class itself.

This approach would also allow changing only a part of your application by adding the dark mode classes on an inner component:

But we’re not done yet. The second level of abstraction would make our code even more robust.

Third Abstraction — Concepts

How can we improve the following code?

What we miss is some kind of abstraction in the conceptual level of the theme.

For example, think of how many components would be red if the red color is the main color of your brand.

In a potential re-branding where the main color of your website changes you would have to run through the many entities of our application and change them one by one.

Also, how would you know what of the red colors should you change to, let’s say, purple? Some might need to stay red- for example error messages.

Here is the same code but with a second level of abstraction:

Now, you only need to change your main brand color and all the many entities that are relying on it would be updated to the new color.

Here is a sandbox where this theme is being used. Notice how conveniently the changes to variables in the different abstraction levels of the theme affect each other and the website: