{"id":355683,"date":"2021-11-11T07:28:04","date_gmt":"2021-11-11T15:28:04","guid":{"rendered":"https:\/\/css-tricks.com\/?p=355683"},"modified":"2021-11-11T08:43:30","modified_gmt":"2021-11-11T16:43:30","slug":"easy-dark-mode-and-multiple-color-themes-in-react","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/easy-dark-mode-and-multiple-color-themes-in-react\/","title":{"rendered":"Easy Dark Mode (and Multiple Color Themes!) in React"},"content":{"rendered":"\n

I was working on a large React application for a startup, and aside from just wanting some good strategies to keep our styles organized, I wanted to give this whole \u201cdark mode\u201d thing a shot. With the huge ecosystem around React, you might think that there would be a go-to solution for style themes, but a little web searching shows that really isn\u2019t the case.<\/p>\n\n\n\n

There are plenty of different options out there, but many of them tie into very specific CSS strategies, like using CSS Modules, some form of CSS-in-JS, etc. I also found tools specific to certain frameworks, like Gatsby, but not a generic React project. What I was looking for was a basic system that\u2019s easy to set up and work with without jumping through a ton of hoops; something fast, something easy to get a whole team of front-end and full-stack developers onboarded with quickly.<\/p>\n\n\n\n\n\n\n\n

The existing solution I liked the best centered around using CSS variables and data attributes<\/strong>, found in this StackOverflow answer<\/a>. But that also relied on some useRef<\/code> stuff that felt hack-y. As they say in every infomercial ever, there\u2019s got to be a better way!<\/em><\/p>\n\n\n\n

Fortunately, there is. By combining that general CSS variable strategy with the beautiful useLocalStorage<\/a><\/code> hook, we have a powerful, easy-to-use theming system. I\u2019m going to walk through setting this thing up and running it, starting from a brand new React app. And if you stick around to the end, I also show you how to integrate it with react-scoped-css<\/a>, which is what makes this my absolutely preferred way to work with CSS in React.<\/p>\n\n\n

Project setup<\/h3>\n\n\n

<\/a> Let\u2019s pick this up at a very good place to start<\/a>: the beginning.<\/p>\n\n\n\n

This guide assumes a basic familiarity with CSS, JavaScript, and React.<\/p><\/blockquote>\n\n\n\n

First, make sure you have a recent version of Node and npm installed. Then navigate to whatever folder you want your project to live in, run git bash<\/code> there (or your preferred command line tool), then run:<\/p>\n\n\n\n

npx create-react-app easy-react-themes --template typescript<\/code><\/pre>\n\n\n\n

Swap out easy-react-themes<\/code> with the name of your project, and feel free to leave off the --template typescript<\/code> if you\u2019d rather work in JavaScript. I happen to like TypeScript but it genuinely makes no difference for this guide, other than files ending in .ts\/.tsx vs .js\/.jsx.<\/p>\n\n\n\n

Now we\u2019ll open up our brand new project in a code editor. I\u2019m using VS Code for this example, and if you are too, then you can run these commands:<\/p>\n\n\n\n

cd easy-react-themes\ncode .<\/code><\/pre>\n\n\n\n
\"\"
Not much to look at yet, but we\u2019ll change that!<\/figcaption><\/figure>\n\n\n\n

Running npm start<\/code> next starts your development server, and produces this in a new browser window:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

And, finally, go ahead and install the use-local-storage package with:<\/p>\n\n\n\n

npm i use-local-storage<\/code><\/pre>\n\n\n\n

And that\u2019s it for the initial setup of the project!<\/p>\n\n\n

Code setup<\/h3>\n\n\n

Open the App.tsx<\/code> file and get rid of the stuff we don\u2019t need.<\/p>\n\n\n\n

\n
\n
\"\"<\/a>
We want to go from this\u2026<\/figcaption><\/figure>\n<\/div>\n\n\n\n
\n
\"\"<\/a>
\u2026to this.<\/figcaption><\/figure>\n<\/div>\n<\/div>\n\n\n\n

Delete the entire content in App.css<\/code>:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Woot! Now let\u2019s create our themes! Open up the index.css<\/code> file and add this to it:<\/p>\n\n\n\n

:root {\n  --background: white;\n  --text-primary: black;\n  --text-secondary: royalblue;\n  --accent: purple;\n}\n[data-theme='dark'] {\n  --background: black;\n  --text-primary: white;\n  --text-secondary: grey;\n  --accent: darkred;\n}<\/code><\/pre>\n\n\n\n

Here\u2019s what we have so far:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

See what we just did there? If you\u2019re unfamiliar with CSS Custom Properties<\/a> (as also known as CSS variables), they allow us to define a value to be used elsewhere in our stylesheets, with the pattern being --key: value<\/code>. In this case, we\u2019re only defining a few colors and applying them to the :root<\/code> element so they can be used be used wherever else we need them across the whole React project.<\/p>\n\n\n\n

The second part, starting with [data-theme='dark']<\/code>, is where things get interesting. HTML (and JSX, which we\u2019re using to create HTML in React) allows us to set completely arbitrary properties for our HTML elements with the data-*<\/code> attribute. In this case, we are giving the outermost <div><\/code> element of our application a data-theme<\/code> attribute and toggling its value between light<\/code> and dark<\/code>. When it\u2019s dark<\/code>, the CSS[data-theme='dark']<\/code> section overrides the variables we defined in the :root<\/code>, so any styling which relies on those variables is toggled as well.<\/p>\n\n\n\n

Let\u2019s put that into practice. Back in App.tsx<\/code>, let\u2019s give React a way to track the theme state. We\u2019d normally use something like useState<\/code> for local state, or Redux for global state management, but we also want the user\u2019s theme selection to stick around if they leave our app and come back later. While we could use Redux and redux-persist, that\u2019s way overkill for our needs.<\/p>\n\n\n\n

Instead, we\u2019re using the useLocalStorage<\/code> hook we installed earlier. It gives us a way to store things in local storage, as you might expect, but as a React hook, it maintains stateful knowledge of what it\u2019s doing with localStorage<\/code>, making our lives easy.<\/p>\n\n\n\n

Some of you might be thinking \u201cOh no, what if the page renders before our JavaScript checks in with localStorage<\/code> and we get the dreaded \u201cflash of wrong theme?\u201d But you don\u2019t have to worry about that here since our React app is completely rendered client-side; the initial HTML file is basically a skeleton with a with a single <div><\/code> that React attaches the app to. All of the final HTML elements are generated by JavaScript after<\/em> checking localStorage<\/code>.<\/p>\n\n\n\n

So, first, import the hook at the top of App.tsx<\/code> with:<\/p>\n\n\n\n

import useLocalStorage from 'use-local-storage'<\/code><\/pre>\n\n\n\n

Then, inside our App<\/code> component, we use it with:<\/p>\n\n\n\n

const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\nconst [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');\n<\/code><\/pre>\n\n\n\n

This does a few things for us. First, we\u2019re checking if the user has set a theme preference in their browser settings. Then we\u2019re creating a stateful theme<\/code> variable that is tied to localStorage<\/code> and the setTheme<\/code> function to update theme<\/code>. useLocalStorage<\/code> adds a key:value<\/code> pair to localStorage<\/code> if it doesn\u2019t already exist, which defaults to theme: \"light\"<\/code>, unless our matchMedia<\/code> check comes back as true<\/code>, in which case it\u2019s theme: \"dark\"<\/code>. That way, we\u2019re gracefully handling both possibilities of keeping the theme settings for a returning user, or respecting their browser settings by default if we\u2019re working with new users.<\/p>\n\n\n\n

Next, we add a tiny bit of content to the App<\/code> component so we have some elements to style, along with a button and function to actually allow us to toggle the theme.<\/p>\n\n\n\n

\"\"
The finished App.tsx<\/code> file<\/figcaption><\/figure>\n\n\n\n

The secret sauce is on line 14 where we\u2019ve added data-theme={theme}<\/code> to our top-level <div><\/code>. Now, by switching the value of theme<\/code>, we are choosing whether or not to override the CSS variables in :root<\/code> with the ones in the data-theme='dark'<\/code> section of the index.css<\/code> file.<\/p>\n\n\n\n

The last thing we need to do is add some styling that uses those CSS variables we made earlier, and it\u2019ll up and running! Open App.css<\/code> and drop this CSS in there:<\/p>\n\n\n\n

.App {\n  color: var(--text-primary);\n  background-color: var(--background);\n  font-size: large;\n  font-weight: bold;\n  padding: 20px;\n  height: calc(100vh - 40px);\n  transition: all .5s;\n}\nbutton {\n  color: var(--text-primary);\n  background-color: var(--background);\n  border: 2px var(--text-primary) solid;\n  float: right;\n  transition: all .5s;\n}<\/code><\/pre>\n\n\n\n

Now the background and text for the main <div><\/code>, and the background, text, and outline of the <button><\/code> rely on the CSS variables. That means when the theme changes, everything that depends on those variables update as well. Also note that we added transition: all .5s<\/code> to both the App<\/code> and <button><\/code> for a smooth transition between color themes.<\/p>\n\n\n\n

Now, head back to the browser that\u2019s running the app, and here\u2019s what we get:<\/p>\n\n\n\n

\n