Garden Bird Themes
All Posts

Inheritance Theming with CSS Properties

Published by Maureen Holland

I've seen a huge benefit in switching from selector-based to inheritance-based theming at work. CSS custom properties have been the key to unlocking a more flexible and scalable system.

Selector-based theming

If you've ever worked with BEM, this pattern will be familiar to you. The --[modifier] part is used to change a block's appearance.

        
/* button.css */   
.button--1 {
    background-color: red;
}

.button--2 {
    background-color: yellow;
}

.button--3 {
    background-color: pink;
}

.button--4 {
    background-color: green;
}
    

I've called this the "selector-based" approach because it's very selector-heavy. This becomes troublesome when there are many themes that need to be applied to many components for two reasons:

  1. It multiples the number of modifiers you need. If you have 7 themes across 3 components, you need 21 modifier selector classes.
  2. It lacks a source of truth. We're asking components to individually manage color palettes when we really want all components to reference the same palette.

Inheritance-based theming

Think of the onClick prop in a React Button component. A button always does something on click, but it doesn't need to know every possible thing that could be. We pass that information through the prop.

Similarly, a button always has a color palette, but it doesn't need to know every possible palette that could be. We pass that information through the custom property.

Custom properties define shared properties according to a higher level theme selector. This is essentially the Material UI design system's context approach, where custom properties used in component styles are "system" tokens and themed values are "reference" tokens.

        
/* themes.css: define themes */        
.theme--1 {
    --primary-color: red;
}

.theme--2 {
    --primary-color: yellow;
}

.theme--3 {
    --primary-color: pink;
}

.theme--4 {
  --primary-color: green;
}

// button.css: use themed value
.button {
    background-color: var(--primary-color);
}
        
    

Inheritance-based theming allows us to have a constant number of theme selector modifiers, regardless of how many components they affect, and those themes are all managed from one file.

Comparison

Using the above code snippets as a base, consider how each approach would adapt to the following requests.

Theme 2 more components

Changes required in selector approach:

Changes required in inheritance approach:

Add 3 new themes

Changes required in selector approach:

Changes required in inheritance approach:

If you want to explore the final code from each approach after those changes, check out this codepen:

In the selector approach, we end up with 21 modifier selector classes across 3 files. In the inheritance approach, we end up with 7 modifier selector classes in 1 file.

Theming needs vary. Sure, you might have some themes for established products meant to live forever. But you also might have some for marketing campaigns meant to die after a week. Or experimental products that tweak their brand colors regularly.

The extensibility and centralization of inheritance-based theming makes it quick and easy to fulfill new requests and clean up retired themes. If you haven't tried this approach before, I highly recommend it.

More resources: