Adding a Smart Dark mode to my React app

Rafael Pérez García
ImperdibleSoft
Published in
6 min readSep 5, 2019

--

I’m building an application using some of the most used technologies in the frontend scene: Typescript, ReactJS, Redux, Styled Components, ExpressJS and Webpack.

Time ago, I heard about an API that allows webapps to detect the OS dark mode, so you could adapt your styles to the user preferences, and well, I’m very curious, so you guess what happened :).

What do I want to achieve

So, before start coding, you’ll need to know what are my goals and what this post is about. I want my application to be able to switch to dark mode in runtime, so I need different sets of variables, and I need my components to generate their colors when they are rendered.

This will allow the user to switch its device Dark mode option without refreshing the page.

So, stuff that I need:

  • A color palette: A list of variables where I will store all my hex colors and use some “decontextualized names”, such as GREY_1, GREY_2 or WHITE_TRASLUCENT.
  • A theme template: A list of “contextualized variables” where I will assign those colors to my application components, such as HEADER_BACKGROUND, BUTTON_BACKGROUND, BUTTON_BACKGROUND_ACTIVE or BUTTON_BACKGROUND_HOVER.
  • A theme selector: Of course, I need a way to select a theme and store it somewhere, so all my components can access to it and know what color to use. (spoiler alert: did I heard useContext?)
  • A color resolver: A function that, given the colorName and the selectedTheme, it will return the color value. In case the color doesn’t exist in selected theme, or no theme is selected, it will return some default values.

Creating a theme-engine

So, now that I have my ideas more or less clear, it’s time to translate it to code! Based on the statements I already mentioned, I would need these files to create my theme system:

app/
theme/
themes/ // Contextualized color palettes
common.ts
dark.ts
light.ts
colors.ts // Decontextualized color palette
index.ts // Theme selector and color resolver
index.tsx // App's mount point
styled-components.tsx
wrapper.tsx // Where I use my styled components

The color palette

theme/colors.ts is probably the simplest file. It’s just a list of variables containing hex values like these ones:

./app/theme/colors.ts

I think you can imagine the rest of the file :)

The theme template

You may think I could use theme/colors.ts directly in my components. Well, I’ll tell you that if I use GREY_3 in two different components, and I want to change one of them, probably I will modify GREY_3 value and change the two components, wich is bad.

So you may think I could use variables such as HEADER_BACKGROUND and assign them the hex values, like const HEADER_BACKGROUND = '#f374ad'. And you are right, but this approach probably will lead me to have very similar colors and don’t realize about that, so my UI won’t look as much coherent as it could.

That’s why I consider I need a theme template :). Of course, this is a matter of taste, so feel free to do it as it best fits your needs.

Since there are colors that won’t change, such as branded colors (my own brand color, linkedin, instagram, etc), I decided to create a theme/themes/common.ts file where I will expose those common colors, and reuse them in my themes. So, it’ll look like this:

./app/theme/themes/common.ts

Yes. That simple. It’s just a subset of my color palette, so I don’t have to specify them manually on each theme.

Then, the real theme templates (like theme/themes/dark.ts or theme/themes/light.ts) will result in something like this:

./app/theme/themes/dark.ts

The theme selector and color resolver

The last piece of my theme engine is theme/index.ts that is the only file that will be used in the application, and it will provide 2 vital functions for this system to work.

./app/theme/index.ts

Using the theme engine

Now that I have created my engine, I need two things:

  1. Store user preference in some place
  2. Read user preference in my components

Storing selected theme in React Context

So, for the first point, I’m using React context to store the selected theme, and make it accessible to my styled components. And, as I’m using styled-components I can use the context they are providing for this purpose.

So, I’ll go to my app’s root point, and I will wrapp everything with this context, like follows:

./app/index.tsx

You’ll notice that theme prop in ThemeProvider is an object. This is something styled-component decided, and I think it can be useful if you want to apply different themes to different parts of your application. I’m just using one single theme at time, so that’s something anoying for me in certain moments, but it’s only cosmetics!

If you want the user to be able to change, you’ll need to provide the user the ability to change the localStorage to set theme=<selectedTheme>. I haven’t coded that functionality yet, but it shouldn’t be difficult.

Themed components

We are about to finish! The only thing pending is to make our components able to read the context and apply the propper color. This is quite simple, since styled-components is injecting the theme property in our components (if we use the ThemeContext they are providing)

So, the only think we need to do is to check that property in our components:

./app/styled-components.tsx

Now, I just need to use my styled components as usual. If the OS mode changes, or the localStorage property changes, the application detect it on any state change (such as navigating), and will update the entire application theme to the new one!

Conclusion

I have to say that I’m very happy with the result. Probably there are different approaches with different advantages, but this is the one I think best fits my needs right now.

While developing this, I’ve found several pros and cons that you may want to know:

Pros

  • Transparent to the user: In this example, I don’t provide the user a tool for selecting a theme. The app will match user’s OS’ selected theme, so the user doesn’t have to do anything. Just like magic. Despite this, the theme selector supports user preferences :)
  • Easy to maintain: Since the theme templates are abstracted, you can easily change a component’s color from one single and located file, so updating your buttons color for a particular theme should take you less than 10s.
  • Scalable: The example has been made with 2 themes, but you could add infinite themes to this system. In deed, you could even create variants, using object destructuring, to override only certain values of your main theme.
  • Automated: Thanks to the color resolver, you don’t have to take care about conditionals or stuff like that. Also, thanks to styled-component’s ThemeProvider, you don’t need to manually inject the selected theme to each styled component.

Cons

  • Performance: Maybe setting a new context value on each navigation or state change is not the most performant thing in the world. If you are a performance-drama guy, or your application requires a top-level performance, probably, you should polish this approach.
  • Color selection error: Since getColor function accept any string as first parameter, you could misspell the color name you want to select. I solved this by adding Types to all the system, so now the function has autocomplete feature thanks to TS. But if you are not using it, you will face this problem.

So, I hope you ejoyed reading this, and also hope you get something interesting to your skills.

Keep coding!

--

--