Dark Theme with Styled Components

Gal Tadmor
BigPanda Engineering
4 min readMay 23, 2021

The what & the why

The saying goes: “written in black and white”. But can it be the other way around?

Let’s talk about dark theme (AKA dark mode). Dark-theming your app means adding an option to invert your app’s colors. Whereas traditional apps use a light background and a dark text color, some users would prefer to see it the other way around. It can be just a matter of taste, but it can also increase your app’s accessibility, be easier on the eyes, and reduce the battery usage of your customers’ devices. It’s quite simple to add it to any existing app.

At BigPanda, we use React and styled-components. These two are quite popular, and their combination can easily be used to bootstrap a dark theme-able app. Let’s do it step-by-step.

You can see the end result in CodeSandbox.

Project bootstrapping

We’ll start by creating a new React project, using create-react-app:

npx create-react-app darktheme
cd darktheme
npm i
npm start

Now we have a boilerplate app. Let’s edit App.jsx , so all we have is just a page title and a toggling button:

App.jsx

import React, { useState } from 'react';const App = () => {
const [theme, setTheme] = useState("light");
const isDarkTheme = theme === "dark";
const toggleTheme = () => setTheme(isDarkTheme ? "light" : "dark");
return (
<div>
<h1>Hello!</h1>
<button onClick={toggleTheme}>
{isDarkTheme ?
<span aria-label="Light mode" role="img">🌞</span> :
<span aria-label="Dark mode" role="img">🌜</span>}
</button>
</div>
);
}
export default App;

Cool. We can see that the toggling mechanism is working, since the button’s emoji is changing. Now let’s add styled-components to the mix.

Styled Components in a nutshell

styled-components is a popular CSS-in-JS solution. It’s a great library, that can solve many of our common CSS problems, like class names bleeding or vendor prefixing. In our case, it’s great for theming management. We’ll start by installing it:

npm i styled-components

We’ll use two tools that are baked into styled-components: ThemeProvider & createGlobalStyle.

  • ThemeProvider will wrap our entire application, and provide the theme’s context to all of the nested components.
  • createGlobalStyle will enable us to set the global styles, e.g. the body and text color.

Let’s create a new file in the src directory, named theme.js . It will include our global styles and our two color palettes:

theme.js

import { createGlobalStyle } from 'styled-components';export const GlobalStyles = createGlobalStyle`
body {
background: ${({ theme }) => theme.body};
color: ${({ theme }) => theme.text};
transition: background 0.2s ease-in, color 0.2s ease-in;
}
`;
export const lightTheme = {
body: '#f1f1f1',
text: '#121620'
};
export const darkTheme = {
body: '#121620',
text: '#f1f1f1'
};

As you can see, styled components’ syntax is very similar to regular CSS. It uses template literals, which is a cool concept on its own, to set the styling rules of HTML directives & React components. The transition rule will help us animate between the dark and the light themes, and the background and text colors will be set by the selected theme. The light theme has a light background color and a dark text color, and the dark theme has the exact opposite settings.

In App.jsx, we’ll import GlobalStyles, lightTheme and darkTheme, and use ThemeProvider to pass the selected theme to the child components:

App.jsx

import React, { useState } from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme, GlobalStyles } from './theme';const App = () => {
const [theme, setTheme] = useState("light");
const isDarkTheme = theme === "dark";
const toggleTheme = () => setTheme(isDarkTheme ? "light" : "dark");
return (
<ThemeProvider theme={isDarkTheme ? darkTheme : lightTheme}>
<>
<GlobalStyles />
<h1>Hello!</h1>
<button onClick={toggleTheme}>
{isDarkTheme ?
<span aria-label="Light mode" role="img">🌞</span> :
<span aria-label="Dark mode" role="img">🌜</span>}
</button>
</>
</ThemeProvider>
);
}
export default App;

Notice that we’re passing the selected theme as the theme property, so the destructuring in GlobalStyles will work. This theme property will be available in all of the child components, so you don’t have to worry about prop-drilling. You can apply the same logic that we used in GlobalStyles in all of your components, to color anything that’s not inherited from the body’s settings (for example, an input component’s text color).

...and that’s it! We have a dark-themed application!

Bonus points: using the user’s preference

Now our app uses a light theme as its default, and we offer our users an option to switch to the dark theme. That’s great progress. But we can improve it even further, by detecting that a user prefers dark mode, based on his/her system setting, and auto-setting it by default. All thanks to prefers-color-scheme.

In App.jsx, we’ll add a React hook, that will check if the user prefers dark mode. If so, we’ll override the default light mode value. In addition, we can tweak toggleTheme a bit, and save the selected theme in local storage, to make sure that any user interaction will override the default preference:

const toggleTheme = () => {
const updatedTheme = isDarkTheme ? "light" : "dark";
setTheme(updatedTheme);
localStorage.setItem("theme", updatedTheme);
};
useEffect(() => {
const savedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme && ["dark", "light"].includes(savedTheme)) {
setTheme(savedTheme);
} else if (prefersDark) {
setTheme("dark");
}
}, []);

Now your app is more accessible, has a better user experience, and it looks great in dark mode. Not too shabby for just one library & a few lines of code!

--

--

Gal Tadmor
BigPanda Engineering

Engineering Team Lead @ BigPanda. I like frontend better, but don’t tell backend about it. I love metal, soccer & avocados.