Theming styled-components with CSS custom properties

Recently, I made the transition from Vue to React (more about it here). With Vue, I used to write pure CSS in the old-school style tag. Now with the transition to React, I wanted to experience something different, so I decided to give styled-components a try. Honestly, so far I love it. It helps to keep a maintainable styling which is both easy to refactor and expand. The only downside is that if you overuse their props system, your styling can become less readable and messy. This is exactly why I love CSS custom properties (variables), they help you to keep your CSS clean and neat, especially when it comes to theming!

CSS custom properties

It's just like any other variable system you know

CSS custom properties often called CSS variables, contain values that can be reused throughout the application. It's just like any other variable system you know. They are supported on 94.75% of the browser market share, so it's even production-ready. Defining a custom property is like setting a CSS attribute the only difference is that you need to prefix with --.

:root {
  --theme-primary: #F5005E;
}

In the example above, I initialized a new custom property called theme-primary and set its value to #151618. The :root pseudo-class is used to make sure the new custom property is available throughout our document and not just in a specific scope. Now to use theme-primary, we can use the CSS function var. Let's see in action:

{% codepen codepen.io/idoshamun/pen/vYGyMNp default-tab=css,result %}

To read more, check out MDN docs.

CSS custom properties vs styled-components props

CSS custom properties will keep your code clean and readable while styled-components props are much more flexible

You already get a grasp of what CSS custom properties are. But styled-components has a props system as well. So how can we decide which one is better for us? The short answer: CSS custom properties will keep your code clean and readable while styled-components props are much more flexible. Let's dive into it.

On one side, we have CSS custom properties, that gives you all the CSS features out-of-the-box. Variables are automatically passed downstream to children elements, you can override them with more specific rules and change them dynamically just like any other CSS attribute. The styling also remains neat and elegant (more on this later). But at the end of the day, they're just strings you can't do anything fancy with them.

On the other side, styled-components props wield the power of JavaScript. You can use them like any other JavaScript variable and change the style accordingly. You can react to user events, set the value according to the React component props, validate, set types, and basically everything. The problem is that it's much harder to read a style that uses props and the final bundle will be bigger.

Theme properties are usually statically set and are accessed by almost any component in the app. It makes a perfect use case for the CSS custom properties. Passing around the theme is something that we get for free and we don't need to validate or apply any complex rules on them.

styled-components global style

styled-components is focused on creating components with scoped styling. But sometimes, we need to apply global styling to all our components. For example, if we want to reset the browser's default styling or in our case if we need to apply a global theme. For this specific reason, we can use the helper function createGlobalStyle. Let's update our previous example to use styled-components and createGlobalStyle.

{% codepen codepen.io/idoshamun/pen/VwaPKKM default-tab=js,result %}

Theming

We're almost done, this is the final step in the process. Here we will integrate everything we learned to create a maintainable theme system for our React application. To create the themes we need to set the same CSS custom properties multiple times with different values. So for example, let's say we have a dark theme (our default) and a light theme. The dark values will be set in the :root pseudo-element as default, and the light values can be set in html.light. It will override the default values in case the html element has the light class toggled on.

Let's see it in action!

{% codepen codepen.io/idoshamun/pen/BaKpLmM default-tab=js,result %}

Look how easy it is! Once the custom properties are changed, it's automatically propagated down to every component.

For comparison purposes, I created the same demo with styled-components built-in solution. (For more info click here)

{% codepen codepen.io/idoshamun/pen/xxVgEaL default-tab=js,result %}

There are two cons to this implementation, in my opinion. First, I find it less readable and elegant solution as it involves too much string interpolations. Second, the output bundle is larger as styled-components create a class for each value of the prop. If you open the DevTools while toggling the theme you will see the button class changes.

At the end of the day, it's up to you to decide which solution you like more. There is no right or wrong.

BONUS: Transitioning the theme change

You made it so far, so here's a nice reward. It's always better to transition smoothly UI changes and it's true for themes as well. All we have to do is upon toggle the theme to add a transition. This will make sure every attribute is smoothly transitioned. Please use it with care as it may cause performance issues in some cases.

{% codepen codepen.io/idoshamun/pen/mdPRrva default-tab=js,result %}

And that's all! I'm glad you made it through. Let me know what you think


Daily delivers the best programming news every new tab. We will rank hundreds of qualified sources for you so that you can hack the future.

Daily Poster

No Comments Yet