Creating a dark mode with styled components

By Jack,

May 2019

Web Dev
The web is light and full of terrors, old man, but dark mode removes them all - Melisandre.

Everywhere you look lately you see the headline ‘your favourite app, now in dark mode’. Never being one to miss out on a bandwagon, I wanted to play around with the idea of implementing dark mode on a web project.

Preflight safety info

I'm going to be using Gatsby.js for this project example, however this can also be done easily in a regular React.js project too. I've created this toggle using styled components 💅. Check out the docs for more info.

Styled components is a CSS-in-JS styling framework that uses tagged template literals in JavaScript to provide you platform that allows you to write actual CSS to style React components.

We need to install styled-components into our project & since I'm using Gatsby.js I'll be using the Gatsby specific version of the styled components plugin. Use the regular npm package if you're using React.js.

I'm using Yarn to install these packages but npm works too.

yarn add gatsby-plugin-styled-components styled-components babel-plugin-styled-components

Tweak the layout file

We need to convert our layout component to a class based component so that we can write the logic needed to change the theme by clicking on a button.

class Layout extends React.Component {


 render() {
   const { children } = this.props

   return (
       <>
         <Header
           siteTitle={"Gatsby Dark Theme Blog"}
         />
         <div>
           {children}
         </div>
       </>
   )
 }
}

export default Layout

Getting down to business

Let's import the ThemeProvider component from styled-components into our project and then create the two themes that you'll want to switch between.

import { ThemeProvider } from "styled-components"

const light = {
  main: "#2a2a2a",
  secondary: "white",
  link: "white",
  brand: "rebeccapurple",
  body: "white",
}

const dark = {
  main: "white",
  secondary: "#2a2a2a",
  link: "white",
  brand: "palevioletred",
  body: "#2a2a2a",
}

Setting state and using local storage

Next we're going to set state and bind a function in our constructor, so that when we write our function to change the theme in the next step we can access 'this'.

constructor(props) {
   super(props)
   this.state = { lightTheme: true }
   this.changeTheme = this.changeTheme.bind(this)
 }

Our next step is writing a function that will be fired when the user clicks a button that shows the different color themes in the project.

This function needs to set the state to the opposite of what it currently is (light to dark or vice versa) and then save that value (which will be a boolean in our case) in state and in local storage.

We do this to ensure that when a user transitions to other pages or leaves the site and comes back, their chosen theme remains constant.

changeTheme() {
   this.setState({
     lightTheme: !this.state.lightTheme,
   })
   localStorage.setItem("lightTheme", !this.state.lightTheme)
 }

Using lifecycle methods to maintain theme

Through the magic of local storage, we run a quick check to see if our item we've set in local storage is defined and if it is, we then set state to the value of the item stored.

It’s also important that we parse the result coming back from local storage as the item will be stored as a string when saved to local storage but we need it as a boolean.

componentDidMount() {
   const localStorageLayout = localStorage.getItem("lightTheme")
   if (localStorageLayout) {
     this.setState({ lightTheme: JSON.parse(localStorageLayout) })
   }
 }

Tweak the layout file 2: Electric Boogaloo

Using styled components we need to create a Layout element that will wrap the entire contents of the components (apart from the ThemeProvider), so that when we trigger a color change, the entire website's theme changes.

The Layout element changes colors via props that are accessible via the Theme Provider.

We use this methodology of passing props to the styled components to change the color of all elements on the website.

const LayoutEl = styled.div`
  background-color: ${props =>
    props.theme.secondary ? props.theme.body : undefined};
  color: ${props => (props.theme.secondary ? props.theme.main : undefined)};
`

Wrapping the Theme Provider around EVERYTHING

We now need to wrap our entire project (from within layout.js) with the ThemeProvider from styled-components and then apply our light and dark options to the project, based on the state we change in our changeTheme function. We use a ternary to give the component the correct value, based on the state we toggle in our function.

You’ll then need to wrap the ThemeProvider around the components in layout.js so that the theme is available throughout the whole website.

<ThemeProvider theme={this.state.lightTheme ? light : dark}>
  <LayoutEl>
    <Header
      siteTitle={"Gatsby Dark Theme Blog"}
    />
    <div>
      {children}
    </div>
  </LayoutEl>
</ThemeProvider>

Making the button

I’ve created a toggleTheme.js file and used a simple button element that will allow the user to choose whether they want a light or dark mode.

The content of the button will change depending on the state, showing either 'Dark Mode' or 'Light Mode'.

This components accepts 2 props and these are the state of lightTheme (boolean) and the function changeTheme that we map to the onClick property, so each time the button is clicked, the state changes.

import React from "react"

const ToggleTheme = ({ changeTheme, lightTheme }) => (
 <button onClick={changeTheme}>
   {lightTheme ? "Dark mode" : "Light mode"}
 </button>
)

export default ToggleTheme

It's alive!!

I’ve placed the component in the header, so I need to pass the function and the state to the header before I can pass them to the button component.

<Header
  siteTitle={"Gatsby Dark Theme Blog"}
  changeTheme={this.changeTheme}
  lightTheme={this.state.lightTheme}
/>

Within your header component you can pass those values to the new toggleTheme.js component we’ve made so that when we click the button, the theme changes. Make sure you import the component before placing it in your project.

<ToggleTheme
  changeTheme={this.props.changeTheme}
  lightTheme={this.props.lightTheme}
/>

Further reading

There is also a 'prefers-color-scheme' media query that is being included in the newest versions of web browsers that can register your OS's color scheme and have then have the website's color scheme change accordingly.

An interesting V2 of this blog post would be implementing the media query to work alongside our code above, to automatically set the theme before the user first sets eyes on the page.

So dark, very mysterious, much links

I've put together a basic working example on the link below & I'll include the repo for you to check out too.

https://gatsby-dark-theme-blog.netlify.com/

https://github.com/eagleeyejack/gatsby-dark-theme-blog

Any questions about this blog post? Drop us a tweet.