Check out a free preview of the full Design Systems with React & Storybook course:
The "Making Extensible Themes" Lesson is part of the full, Design Systems with React & Storybook course featured in this preview video. Here's what you'd learn in this lesson:

Emma demonstrates how to create an extensible theme by introducing the React hook useState, and explains how to modify the state and use properties.

Get Unlimited Access Now

Transcript from the "Making Extensible Themes" Lesson

>> So we have one theme. Let's go ahead and make a second theme. I'm gonna copy and paste my entire theme inside of themes.js, and I'm gonna call this darkTheme, okay? If you have designed something in Figma, I saw some really great designs for the modals and for the buttons that actually already were dark themed.

[00:00:17] Feel free to do a light theme. Just do something different, something that can differentiate between two different themes. All right, so I'm gonna change this to neutral[100], neutral[200], so we're essentially just kinda like flopping all these values here. It's gonna be neutral[300]. My textColor is now gonna be blue[300].

[00:00:41] My textColorInverted is gonna be neutral[100]. My textColor, I lied, this is gonna be blue[300], okay? textColorOnPrimary is also gonna be blue[300]. disabled I'm gonna keep as neutral[400]. textOnDisabled is also gonna be the same. Our font is the same and I'm just gonna keep our statuses the same as well.

[00:01:10] You'll notice that if I was actually building a dark theme, a lot of these colors are not going to be appropriate. But we don't need to worry about if they're accessible or not for today. If you are building a system, please make them accessible. This is kind of just for demonstration purposes.

[00:01:25] So now that we've got two themes, let's use some state. Let's add some state to our application and see if we can make this work. Okay, so inside of index.js, we're gonna import {useState} from "react". We are also going to import our dark theme and our default theme from our utils.

[00:01:45] So darkTheme or whatever your new theme is, and our defaultTheme. So right now we're just returning some JSX. And if we want to add some state, it has to be top level. So I'm gonna refactor this. Oops, this is gonna be our return statement. Here's our JSX we're gonna return.

[00:02:02] Now let's add some state. So const[useDarkTheme and this is gonna be setUseDarkTheme. This is React Hooks. We're gonna initialize this to false. Okay, so if you haven't worked with Hooks, I definitely would recommend checking it out. useState is kind of the basic one here. What we're saying is, we want some state, here's our default value, it's false.

[00:02:24] And when you call this, it's gonna return an array with two items in it. We are destructuring them out into two different variables. This is gonna be our actual state value and this gonna be our updater function. So now that we have state, we can actually go and try to toggle some of this.

[00:02:42] So I saw some of you in the chat asking why we weren't using theme provider. The short answer is we are. I just wanted to walk you through the process of how you would build this incrementally. So let's import themeProvider from styled components. import {ThemeProvider} from "styled-components", okay?

[00:03:01] ThemeProvider is gonna allow us to essentially pass our theme down through all of our different components. All we have to do now is replace this wrapping div with ThemeProvider, and we have to give it a theme that we want it to actually use. Well, now we know we've got some state about which theme we're using.

[00:03:22] So let's say our theme, we're gonna be a JSX expression. So, if we are using darkTheme, okay, we wanna use our darkTheme. Otherwise we want to use our defaultTheme. Now if you have multiple themes, maybe you have a bunch of radio buttons, maybe you have a select dropdown.

[00:03:39] This is obviously gonna change. You wouldn't use a Boolean for your state value at that point. You might use an enum with different values, things like that. But we just have two, so we'll just toggle between them. We now actually need a way to toggle through these different themes, so we need a way to actually update our state.

[00:03:56] So what I'm gonna do is just create a couple of buttons here to do that. I could use our buttons, I'm just gonna use default buttons. I'm gonna show you two. If you haven't worked with React inline styling, you can also just do it this way. I'm just gonna set a couple of different style properties.

[00:04:12] So I'm just gonna give this a margin of "0 16 px 24 px", so this will set zero margin on the top, left and right will have 16, and bottom will have 24. I'm gonna give it padding of 8px and a background of none. Okay, I'm just gonna say here dark theme, and I also just need to add an onClick handler here to update our state.

[00:04:46] So onClick, we wanna update our state. So setUseDarkTheme, and I'm gonna set it to true. So if we click this dark theme button, we wanna use dark theme. We also need to do the default theme. So I'm just gonna copy and paste that. It'll have the same styling.

[00:05:06] This will be our default theme. And this will just setUseDarkTheme to false. Okay, very simple. The one other thing I'm gonna do is wrap our primary, secondary, and tertiary buttons in a div with a little bit of styling. We need to actually be able to change the background color so we can see what our dark theme would look like on our components.

[00:05:35] So I'm gonna wrap all of our buttons here in a div, And on this div, I'm just gonna set a couple of styles here. So, Our background color, we want to change. So if we're using our dark theme I want our background color to be the primary color of our default theme.

[00:05:57] So defaultTheme.primaryColor. Otherwise, I want to use our dark theme primary color. If we didn't do this, it'd be really hard to kind of tell what our components look like in the opposite theme. So this is just making that a little easier. I'm gonna set this to a width of a 100vw, height to 100vh.

[00:06:19] This is just to make sure we can see our components. I'm also gonna use some flexbox. So let's display: "flex", alignItems: center. This is what I was talking about earlier. So when you do in-line styling with React, these are actually camel-case, versus when we use style components, it's just plain CSS, which is a lot nicer.

[00:06:42] And I'll just do justifyContent: "space-around". So this should give us a nice little visual. So if we come back here, we can see we've got a couple of buttons up here. We've got our dark theme and our default theme button. We've got our buttons here rendering. And now if I set dark theme, it changes the background to dark theme.

[00:07:02] I'm gonna remove these modifiers because like I said before, I didn't actually change the modifiers in the themes. They're just the same. So we can't actually tell that our theme is working. I'm gonna remove our modifiers. So if I change to dark theme, this is not working. So, right now, if I toggle to dark theme, my components are not actually rendering the dark theme styling.

[00:07:28] Can anyone tell me, does anyone have a guess maybe as to why? This is our button code here. So which-
>> You're using default theme.
>> I am using default theme. So this is not actually pulling any of the state information from our theme provider. This is just simply using default theme and that's the problem here.

[00:07:46] Great, that was great, Matt. So in order to use this anywhere that we see this defaultTheme., I'm going to Cmd + D. I'm gonna highlight all of this. All right, that's a lot of instances. Actually, I don't wanna just do it. Let's go through this button modifier section.

[00:08:07] Let's do an example here. So we've got defaultTheme.status. These are gonna accept properties now, so ThemeProvider is going to pass a property. So props. If we set this to props.status, Should work. So the modifiers are different because they're actually taking in parameters here, they're taking in props here.

[00:08:30] So we can do this here. Shoot, So if I select all of the different states here in our modifiers props, destructure that out, you can just set props here. Props. It's gonna take a second for me to type out. So anywhere here where I have this open and closed parentheses, I wanna select that.

[00:09:03] And I wanna fill those with props. And then what I can do is just, again, let's select all of these defaultTheme, I'm gonna replace you with props. You'll see, so the modifiers are gonna be a little bit different of a format than the traditional buttons, and I'll show you in just a second.

[00:09:27] That's why I'm not selecting all of these. So with modifiers, we have these opening and closing parentheses that takes a parameter and it's gonna return this CSS, okay? But when we're actually creating styled components like we do here, we don't have those opening and closing parentheses. So what we need to do is we're gonna take props as an argument and then we can replace this defaultTheme., okay?

[00:09:53] So this will be props.primaryHoverColor. So I'm just gonna go ahead, this will take just a second. That's the only problem with not being able to find and replace all, cuz they have different structures. I think I can safely select those though. So just be careful when you're auto selecting that you don't accidentally put the wrong format.

[00:10:23] So, props. So if I messed up horribly we'll find out real easy.
>> Is it props.Theme?
>> Yes, thank you [LAUGH]. Let me do, where'd you go, props.? So our styles weren't being applied originally because we were just hard coding in these defaultTheme.values. We can remove this now from our import.

[00:10:59] So again for the modifiers, we can simply destructure props and we can say props.theme. And then whatever we're using. In this case, status is also nested. In our actual components here, wherever you have this expression, wherever you need to evaluate the color, you take props as an argument, and then call props.theme.

[00:11:19] And then whatever value. All right, any questions? This is what it looks like. By the way, if we toggle we can see, it's not working, okay?