{"id":296088,"date":"2019-09-25T07:10:14","date_gmt":"2019-09-25T14:10:14","guid":{"rendered":"https:\/\/css-tricks.com\/?p=296088"},"modified":"2019-09-25T07:10:14","modified_gmt":"2019-09-25T14:10:14","slug":"a-dark-mode-toggle-with-react-and-themeprovider","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/a-dark-mode-toggle-with-react-and-themeprovider\/","title":{"rendered":"A Dark Mode Toggle with React and ThemeProvider"},"content":{"rendered":"

I like when websites have a dark mode option. Dark mode<\/a> makes web pages easier for me to read and helps my eyes feel more relaxed. Many websites, including YouTube and Twitter, have implemented it already, and we\u2019re starting to see it trickle onto many other sites as well.<\/p>\n

In this tutorial, we\u2019re going to build a toggle that allows users to switch between light and dark modes, using a <ThemeProvider<\/a><\/code> wrapper from the styled-components<\/a> library. We\u2019ll create a useDarkMode<\/code> custom hook, which supports the prefers-color-scheme<\/a><\/code> media query to set the mode according to the user\u2019s OS<\/abbr> color scheme settings.<\/p>\n

If that sounds hard, I promise it\u2019s not! Let\u2019s dig in and make it happen.<\/p>\n

<\/p>\n

\n See the Pen
\n Day\/night mode switch toggle with React and ThemeProvider<\/a> by Maks Akymenko (
@maximakymenko<\/a>)
\n on
CodePen<\/a>.<\/span>\n<\/p>\n

Let\u2019s set things up<\/h3>\n

We\u2019ll use create-react-app<\/a> to initiate a new project:<\/p>\n

npx create-react-app my-app\r\ncd my-app\r\nyarn start<\/code><\/pre>\n

Next, open a separate terminal window and install styled-components:<\/p>\n

yarn add styled-components<\/code><\/pre>\n

Next thing to do is create two files. The first is global.js<\/code>, which will contain our base styling, and the second is theme.js<\/code>, which will include variables for our dark and light themes:<\/p>\n

\/\/ theme.js\r\nexport const lightTheme = {\r\n  body: '#E2E2E2',\r\n  text: '#363537',\r\n  toggleBorder: '#FFF',\r\n  gradient: 'linear-gradient(#39598A, #79D7ED)',\r\n}\r\n\r\nexport const darkTheme = {\r\n  body: '#363537',\r\n  text: '#FAFAFA',\r\n  toggleBorder: '#6B8096',\r\n  gradient: 'linear-gradient(#091236, #1E215D)',\r\n}<\/code><\/pre>\n

Feel free to customize variables any way you want, because this code is used just for demonstration purposes. <\/p>\n

\/\/ global.js\r\n\/\/ Source: https:\/\/github.com\/maximakymenko\/react-day-night-toggle-app\/blob\/master\/src\/global.js#L23-L41\r\n\r\nimport { createGlobalStyle } from 'styled-components';\r\n\r\nexport const GlobalStyles = createGlobalStyle`\r\n  *,\r\n  *::after,\r\n  *::before {\r\n    box-sizing: border-box;\r\n  }\r\n\r\n  body {\r\n    align-items: center;\r\n    background: ${({ theme }) => theme.body};\r\n    color: ${({ theme }) => theme.text};\r\n    display: flex;\r\n    flex-direction: column;\r\n    justify-content: center;\r\n    height: 100vh;\r\n    margin: 0;\r\n    padding: 0;\r\n    font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\r\n    transition: all 0.25s linear;\r\n  }<\/code><\/pre>\n

Go to the App.js file. We\u2019re going to delete everything in there and add the layout for our app. Here\u2019s what I did:<\/p>\n

import React from 'react';\r\nimport { ThemeProvider } from 'styled-components';\r\nimport { lightTheme, darkTheme } from '.\/theme';\r\nimport { GlobalStyles } from '.\/global';\r\n\r\nfunction App() {\r\n  return (\r\n    <ThemeProvider theme={lightTheme}>\r\n      <>\r\n        <GlobalStyles \/>\r\n        <button>Toggle theme<\/button>\r\n        <h1>It's a light theme!<\/h1>\r\n        <footer>\r\n        <\/footer>\r\n      <\/>\r\n    <\/ThemeProvider>\r\n  );\r\n}\r\n\r\nexport default App;<\/code><\/pre>\n

This imports our light and dark themes. The ThemeProvider<\/code> component also gets imported and is passed the light theme (lightTheme<\/code>) styles inside. We also import GlobalStyles<\/code> to tighten everything up in one place.<\/p>\n

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

\"\"<\/figure>\n

Now, the toggling functionality<\/h3>\n

There is no magic switching between themes yet, so let\u2019s implement toggling functionality. We are only going to need a couple lines of code to make it work.<\/p>\n

First, import the useState<\/code> hook from react<\/code>:<\/p>\n

\/\/ App.js\r\nimport React, { useState } from 'react';<\/code><\/pre>\n

Next, use the hook to create a local state which will keep track of the current theme and add a function to switch between themes on click: <\/p>\n

\/\/ App.js\r\nconst [theme, setTheme] = useState('light');\r\n\r\n\/\/ The function that toggles between themes\r\nconst toggleTheme = () => {\r\n  \/\/ if the theme is not light, then set it to dark\r\n  if (theme === 'light') {\r\n    setTheme('dark');\r\n  \/\/ otherwise, it should be light\r\n  } else {\r\n    setTheme('light');\r\n  }\r\n}<\/code><\/pre>\n

After that, all that\u2019s left is to pass this function to our button element and conditionally change the theme. Take a look: <\/p>\n

\/\/ App.js\r\nimport React, { useState } from 'react';\r\nimport { ThemeProvider } from 'styled-components';\r\nimport { lightTheme, darkTheme } from '.\/theme';\r\nimport { GlobalStyles } from '.\/global';\r\n\r\n\/\/ The function that toggles between themes\r\nfunction App() {\r\n  const [theme, setTheme] = useState('light');\r\n  const toggleTheme = () => {\r\n    if (theme === 'light') {\r\n      setTheme('dark');\r\n    } else {\r\n      setTheme('light');\r\n    }\r\n  }\r\n  \r\n  \/\/ Return the layout based on the current theme\r\n  return (\r\n    <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>\r\n      <>\r\n        <GlobalStyles \/>\r\n        \/\/ Pass the toggle functionality to the button\r\n        <button onClick={toggleTheme}>Toggle theme<\/button>\r\n        <h1>It's a light theme!<\/h1>\r\n        <footer>\r\n        <\/footer>\r\n      <\/>\r\n    <\/ThemeProvider>\r\n  );\r\n}\r\n\r\nexport default App;<\/code><\/pre>\n
\"\"<\/figure>\n

How does it work?<\/h3>\n
\/\/ global.js\r\nbackground: ${({ theme }) => theme.body};\r\ncolor: ${({ theme }) => theme.text};\r\ntransition: all 0.25s linear;<\/code><\/pre>\n

Earlier in our GlobalStyles<\/code>, we assigned background<\/code> and color<\/code> properties to values from the theme<\/code> object, so now, every time we switch the toggle, values change depending on the darkTheme<\/code> and lightTheme<\/code> objects that we are passing to ThemeProvider<\/code>. The transition<\/code> property allows us to make this change a little more smoothly than working with keyframe animations.<\/p>\n

Now we need the toggle component<\/h3>\n

We\u2019re generally done here because you now know how to create toggling functionality. However, we can always do better, so let\u2019s improve the app by creating a custom Toggle<\/code> component and make our switch functionality reusable. That\u2019s one of the key benefits to making this in React, right?<\/p>\n

We\u2019ll keep everything inside one file for simplicity\u2019s sake,, so let\u2019s create a new one called Toggle.js<\/code> and add the following: <\/p>\n

\/\/ Toggle.js\r\nimport React from 'react'\r\nimport { func, string } from 'prop-types';\r\nimport styled from 'styled-components';\r\n\/\/ Import a couple of SVG files we'll use in the design: https:\/\/www.flaticon.com\r\nimport { ReactComponent as MoonIcon } from 'icons\/moon.svg';\r\nimport { ReactComponent as SunIcon } from 'icons\/sun.svg';\r\n\r\nconst Toggle = ({ theme, toggleTheme }) => {\r\n  const isLight = theme === 'light';\r\n  return (\r\n    <button onClick={toggleTheme} >\r\n      <SunIcon \/>\r\n      <MoonIcon \/>\r\n    <\/button>\r\n  );\r\n};\r\n\r\nToggle.propTypes = {\r\n  theme: string.isRequired,\r\n  toggleTheme: func.isRequired,\r\n}\r\n\r\nexport default Toggle;<\/code><\/pre>\n

You can download icons from here<\/a> and here<\/a>. Also, if we want to use icons as components, remember about importing them as React components<\/a>.<\/p>\n

We passed two props inside: the theme<\/code> will provide the current theme (light or dark) and toggleTheme<\/code> function will be used to switch between them. Below we created an isLight<\/code> variable, which will return a boolean value depending on our current theme. We\u2019ll pass it later to our styled component.<\/p>\n

We\u2019ve also imported a styled<\/code> function from styled-components, so let\u2019s use it. Feel free to add this on top your file after the imports or create a dedicated file for that (e.g. Toggle.styled.js) like I have below. Again, this is purely for presentation purposes, so you can style your component as you see fit.<\/p>\n

\/\/ Toggle.styled.js\r\nconst ToggleContainer = styled.button`\r\n  background: ${({ theme }) => theme.gradient};\r\n  border: 2px solid ${({ theme }) => theme.toggleBorder};\r\n  border-radius: 30px;\r\n  cursor: pointer;\r\n  display: flex;\r\n  font-size: 0.5rem;\r\n  justify-content: space-between;\r\n  margin: 0 auto;\r\n  overflow: hidden;\r\n  padding: 0.5rem;\r\n  position: relative;\r\n  width: 8rem;\r\n  height: 4rem;\r\n\r\n  svg {\r\n    height: auto;\r\n    width: 2.5rem;\r\n    transition: all 0.3s linear;\r\n    \r\n    \/\/ sun icon\r\n    &:first-child {\r\n      transform: ${({ lightTheme }) => lightTheme ? 'translateY(0)' : 'translateY(100px)'};\r\n    }\r\n    \r\n    \/\/ moon icon\r\n    &:nth-child(2) {\r\n      transform: ${({ lightTheme }) => lightTheme ? 'translateY(-100px)' : 'translateY(0)'};\r\n    }\r\n  }\r\n`;<\/code><\/pre>\n

Importing icons as components allows us to directly change the styles of the SVG icons. We\u2019re checking if the lightTheme<\/code> is an active one, and if so, we move the appropriate icon out of the visible area \u2014 sort of like the moon going away when it\u2019s daytime and vice versa.<\/p>\n

Don\u2019t forget to replace the button with the ToggleContainer<\/code> component in Toggle.js, regardless of whether you\u2019re styling in separate file or directly in Toggle.js. Be sure to pass the isLight<\/code> variable to it to specify the current theme. I called the prop lightTheme<\/code> so it would clearly reflect its purpose.<\/p>\n

The last thing to do is import our component inside App.js and pass required props to it. Also, to add a bit more interactivity, I\u2019ve passed condition to toggle between “light” and \u201cdark” in the heading when the theme changes:<\/p>\n

\/\/ App.js\r\n<Toggle theme={theme} toggleTheme={toggleTheme} \/>\r\n<h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!<\/h1><\/code><\/pre>\n

Don\u2019t forget to credit the flaticon.com<\/a> authors for the providing the icons.<\/p>\n

\/\/ App.js\r\n<span>Credits:<\/span>\r\n<small><b>Sun<\/b> icon made by <a href=\"https:\/\/www.flaticon.com\/authors\/smalllikeart\">smalllikeart<\/a> from <a href=\"https:\/\/www.flaticon.com\">www.flaticon.com<\/a><\/small>\r\n<small><b>Moon<\/b> icon made by <a href=\"https:\/\/www.freepik.com\/home\">Freepik<\/a> from <a href=\"https:\/\/www.flaticon.com\">www.flaticon.com<\/a><\/small><\/code><\/pre>\n

Now that\u2019s better: <\/p>\n

\"\"<\/figure>\n

The useDarkMode hook<\/h3>\n

While building an application, we should keep in mind that the app must be scalable, meaning, reusable, so we can use in it many places, or even different projects.<\/p>\n

That is why it would be great if we move our toggle functionality to a separate place \u2014 so, why not to create a dedicated account hook for that?<\/p>\n

Let\u2019s create a new file called useDarkMode.js in the project src<\/code> directory and move our logic into this file with some tweaks:<\/p>\n

\/\/ useDarkMode.js\r\nimport { useEffect, useState } from 'react';\r\n\r\nexport const useDarkMode = () => {\r\n  const [theme, setTheme] = useState('light');\r\n  const toggleTheme = () => {\r\n    if (theme === 'light') {\r\n      window.localStorage.setItem('theme', 'dark')\r\n      setTheme('dark')\r\n    } else {\r\n      window.localStorage.setItem('theme', 'light')\r\n      setTheme('light')\r\n    }\r\n  };\r\n\r\n  useEffect(() => {\r\n    const localTheme = window.localStorage.getItem('theme');\r\n    localTheme && setTheme(localTheme);\r\n  }, []);\r\n\r\n  return [theme, toggleTheme]\r\n};<\/code><\/pre>\n

We\u2019ve added a couple of things here. We want our theme to persist between sessions in the browser, so if someone has chosen a dark theme, that\u2019s what they\u2019ll get on the next visit to the app. That\u2019s a huge UX<\/abbr> improvement. For this reasons we use localStorage<\/code>.<\/p>\n

We\u2019ve also implemented the useEffect<\/a><\/code> hook to check on component mounting. If the user has previously selected a theme, we will pass it to our setTheme<\/code> function. In the end, we will return our theme<\/code>, which contains the chosen theme<\/code> and toggleTheme<\/code> function to switch between modes.<\/p>\n

Now, let\u2019s implement the useDarkMode<\/code> hook. Go into App.js, import the newly created hook, destructure our theme<\/code> and toggleTheme<\/code> properties from the hook, and, put them where they belong:<\/p>\n

\/\/ App.js\r\nimport React from 'react';\r\nimport { ThemeProvider } from 'styled-components';\r\nimport { useDarkMode } from '.\/useDarkMode';\r\nimport { lightTheme, darkTheme } from '.\/theme';\r\nimport { GlobalStyles } from '.\/global';\r\nimport Toggle from '.\/components\/Toggle';\r\n\r\nfunction App() {\r\n  const [theme, toggleTheme] = useDarkMode();\r\n  const themeMode = theme === 'light' ? lightTheme : darkTheme;\r\n\r\n  return (\r\n    <ThemeProvider theme={themeMode}>\r\n      <>\r\n        <GlobalStyles \/>\r\n        <Toggle theme={theme} toggleTheme={toggleTheme} \/>\r\n        <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!<\/h1>\r\n        <footer>\r\n          Credits:<\/span>\r\n          <small>Sun<\/b> icon made by smalllikeart<\/a> from www.flaticon.com<\/a><\/small>\r\n          <small>Moon<\/b> icon made by Freepik<\/a> from www.flaticon.com<\/a><\/small>\r\n        <\/footer>\r\n      <\/>\r\n    <\/ThemeProvider>\r\n  );\r\n}\r\n\r\nexport default App;<\/code><\/pre>\n

This almost<\/em> works almost perfectly, but there is one small thing we can do to make our experience better. Switch to dark theme and reload the page. Do you see that the sun icon loads before the moon icon for a brief moment?<\/p>\n

That happens because our useState<\/code> hook initiates the light<\/code> theme initially. After that, useEffect<\/code> runs, checks localStorage<\/code> and only then sets the theme<\/code> to dark<\/code>.<\/p>\n

So far, I found two solutions. The first is to check if there is a value in localStorage<\/code> in our useState<\/code>: <\/p>\n

\/\/ useDarkMode.js\r\nconst [theme, setTheme] = useState(window.localStorage.getItem('theme') || 'light');<\/code><\/pre>\n

However, I am not sure if it\u2019s a good practice to do checks like that inside useState<\/code>, so let me show you a second solution, that I\u2019m using.<\/p>\n

This one will be a bit more complicated. We will create another state and call it componentMounted<\/code>. Then, inside the useEffect<\/code> hook, where we check our localTheme<\/code>, we\u2019ll add an else<\/code> statement, and if there is no theme<\/code> in localStorage<\/code>, we\u2019ll add it. After that, we\u2019ll set setComponentMounted<\/code> to true<\/code>. In the end, we add componentMounted<\/code> to our return statement. <\/p>\n

\/\/ useDarkMode.js\r\nimport { useEffect, useState } from 'react';\r\n\r\nexport const useDarkMode = () => {\r\n  const [theme, setTheme] = useState('light');\r\n  const [componentMounted, setComponentMounted] = useState(false);\r\n  const toggleTheme = () => {\r\n    if (theme === 'light') {\r\n      window.localStorage.setItem('theme', 'dark');\r\n      setTheme('dark');\r\n    } else {\r\n      window.localStorage.setItem('theme', 'light');\r\n      setTheme('light');\r\n    }\r\n  };\r\n\r\n  useEffect(() => {\r\n    const localTheme = window.localStorage.getItem('theme');\r\n    if (localTheme) {\r\n      setTheme(localTheme);\r\n    } else {\r\n      setTheme('light')\r\n      window.localStorage.setItem('theme', 'light')\r\n    }\r\n    setComponentMounted(true);\r\n  }, []);\r\n  \r\n  return [theme, toggleTheme, componentMounted]\r\n};<\/code><\/pre>\n

You might have noticed that we\u2019ve got some pieces of code that are repeated. We always try to follow the DRY<\/a> principle while writing the code, and right here we\u2019ve got a chance to use it. We can create a separate function that will set our state and pass theme<\/code> to the localStorage<\/code>. I believe, that the best name for it will be setTheme<\/code>, but we\u2019ve already used it, so let\u2019s call it setMode<\/code>:<\/p>\n

\/\/ useDarkMode.js\r\nconst setMode = mode => {\r\n  window.localStorage.setItem('theme', mode)\r\n  setTheme(mode)\r\n};<\/code><\/pre>\n

With this function in place, we can refactor our useDarkMode.js a little:<\/p>\n

\/\/ useDarkMode.js\r\nimport { useEffect, useState } from 'react';\r\nexport const useDarkMode = () => {\r\n  const [theme, setTheme] = useState('light');\r\n  const [componentMounted, setComponentMounted] = useState(false);\r\n\r\n  const setMode = mode => {\r\n    window.localStorage.setItem('theme', mode)\r\n    setTheme(mode)\r\n  };\r\n\r\n  const toggleTheme = () => {\r\n    if (theme === 'light') {\r\n      setMode('dark');\r\n    } else {\r\n      setMode('light');\r\n    }\r\n  };\r\n\r\n  useEffect(() => {\r\n    const localTheme = window.localStorage.getItem('theme');\r\n    if (localTheme) {\r\n      setTheme(localTheme);\r\n    } else {\r\n      setMode('light');\r\n    }\r\n    setComponentMounted(true);\r\n  }, []);\r\n\r\n  return [theme, toggleTheme, componentMounted]\r\n};<\/code><\/pre>\n

We\u2019ve only changed code a little, but it looks so much better and is easier to read and understand! <\/p>\n

Did the component mount?<\/h3>\n

Getting back to componentMounted<\/code> property. We will use it to check if our component has mounted because this is what happens in useEffect<\/code> hook. <\/p>\n

If it hasn\u2019t happened yet, we will render an empty div:<\/p>\n

\/\/ App.js\r\nif (!componentMounted) {\r\n  return <div \/>\r\n};<\/code><\/pre>\n

Here is how complete code for the App.js: <\/p>\n

\/\/ App.js\r\nimport React from 'react';\r\nimport { ThemeProvider } from 'styled-components';\r\nimport { useDarkMode } from '.\/useDarkMode';\r\nimport { lightTheme, darkTheme } from '.\/theme';\r\nimport { GlobalStyles } from '.\/global';\r\nimport Toggle from '.\/components\/Toggle';\r\n\r\nfunction App() {\r\n  const [theme, toggleTheme, componentMounted] = useDarkMode();\r\n\r\n  const themeMode = theme === 'light' ? lightTheme : darkTheme;\r\n\r\n  if (!componentMounted) {\r\n    return <div \/>\r\n  };\r\n\r\n  return (\r\n    <ThemeProvider theme={themeMode}>\r\n      <>\r\n        <GlobalStyles \/>\r\n        <Toggle theme={theme} toggleTheme={toggleTheme} \/>\r\n        <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!<\/h1>\r\n        <footer>\r\n          <span>Credits:<\/span>\r\n          <small><b>Sun<\/b> icon made by <a href=\"https:\/\/www.flaticon.com\/authors\/smalllikeart\">smalllikeart<\/a> from <a href=\"https:\/\/www.flaticon.com\">www.flaticon.com<\/a><\/small>\r\n          <small><b>Moon<\/b> icon made by <a href=\"https:\/\/www.freepik.com\/home\">Freepik<\/a> from <a href=\"https:\/\/www.flaticon.com\">www.flaticon.com<\/a><\/small>\r\n        <\/footer>\r\n      <\/>\r\n    <\/ThemeProvider>\r\n  );\r\n}\r\n\r\nexport default App;<\/code><\/pre>\n

Using the user\u2019s preferred color scheme<\/h3>\n

This part is not required, but it will let you achieve even better user experience. This media feature is used to detect if the user has requested the page to use a light or dark color theme based on the settings in their OS<\/abbr>. For example, if a user\u2019s default color scheme on a phone or laptop is set to dark, your website will change its color scheme accordingly to it. It\u2019s worth noting that this media query is still a work in progress and is included in the Media Queries Level 5 specification<\/a>, which is in Editor\u2019s Draft.<\/p>\n

This browser support data is from Caniuse<\/a>, which has more detail. A number indicates that browser supports the feature at that version and up.<\/p><\/div>

Desktop<\/h4>
Chrome<\/span><\/th>Firefox<\/span><\/th>IE<\/span><\/th>Edge<\/span><\/th>Safari<\/span><\/th><\/tr><\/thead>
76<\/span><\/td>67<\/span><\/td>No<\/span><\/td>79<\/span><\/td>12.1<\/span><\/td><\/tr><\/table><\/div>

Mobile \/ Tablet<\/h4>
Android Chrome<\/span><\/th>Android Firefox<\/span><\/th>Android<\/span><\/th>iOS Safari<\/span><\/th><\/tr><\/thead>
123<\/span><\/td>124<\/span><\/td>123<\/span><\/td>13.0-13.1<\/span><\/td><\/tr><\/table><\/div><\/div>\n

The implementation is pretty straightforward. Because we\u2019re working with a media query, we need to check if the browser supports it in the useEffect<\/code> hook and set appropriate theme. To do that, we\u2019ll use window.matchMedia<\/a><\/code> to check if it exists and whether dark mode is supported. We also need to remember about the localTheme<\/code> because, if it\u2019s available, we don\u2019t want to overwrite it with the dark value unless, of course, the value is set to light.<\/p>\n

If all checks are passed, we will set the dark theme.<\/p>\n

\/\/ useDarkMode.js\r\nuseEffect(() => {\r\nif (\r\n  window.matchMedia &&\r\n  window.matchMedia('(prefers-color-scheme: dark)').matches && \r\n  !localTheme\r\n) {\r\n  setTheme('dark')\r\n  }\r\n})<\/code><\/pre>\n

As mentioned before, we need to remember about the existence of localTheme<\/code> \u2014 that\u2019s why we need to implement our previous logic where we\u2019ve checked for it.<\/p>\n

Here\u2019s what we had from before:<\/p>\n

\/\/ useDarkMode.js\r\nuseEffect(() => {\r\nconst localTheme = window.localStorage.getItem('theme');\r\n  if (localTheme) {\r\n    setTheme(localTheme);\r\n  } else {\r\n    setMode('light');\r\n  }\r\n})<\/code><\/pre>\n

Let\u2019s mix it up. I\u2019ve replaced the if and else statements with ternary operators to make things a little more readable as well: <\/p>\n

\/\/ useDarkMode.js\r\nuseEffect(() => {\r\nconst localTheme = window.localStorage.getItem('theme');\r\nwindow.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?\r\n  setMode('dark') :\r\n  localTheme ?\r\n    setTheme(localTheme) :\r\n    setMode('light');})\r\n})<\/code><\/pre>\n

Here\u2019s the userDarkMode.js file with the complete code:<\/p>\n

\/\/ useDarkMode.js\r\nimport { useEffect, useState } from 'react';\r\n\r\nexport const useDarkMode = () => {\r\n  const [theme, setTheme] = useState('light');\r\n  const [componentMounted, setComponentMounted] = useState(false);\r\n  const setMode = mode => {\r\n    window.localStorage.setItem('theme', mode)\r\n    setTheme(mode)\r\n  };\r\n\r\n  const toggleTheme = () => {\r\n    if (theme === 'light') {\r\n      setMode('dark')\r\n    } else {\r\n      setMode('light')\r\n    }\r\n  };\r\n\r\n  useEffect(() => {\r\n    const localTheme = window.localStorage.getItem('theme');\r\n    window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?\r\n      setMode('dark') :\r\n      localTheme ?\r\n        setTheme(localTheme) :\r\n        setMode('light');\r\n    setComponentMounted(true);\r\n  }, []);\r\n\r\n  return [theme, toggleTheme, componentMounted]\r\n};<\/code><\/pre>\n

Give it a try! It changes the mode, persists the theme in localStorage<\/code>, and also sets the default theme accordingly to the OS<\/abbr> color scheme if it\u2019s available. <\/p>\n

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

Congratulations, my friend! Great job! If you have any questions about implementation, feel free to send me a message<\/a>! <\/p>\n","protected":false},"excerpt":{"rendered":"

I like when websites have a dark mode option. Dark mode makes web pages easier for me to read and helps my eyes feel more relaxed. Many websites, including YouTube and Twitter, have implemented it already, and we\u2019re starting to see it trickle onto many other sites as well. In this tutorial, we\u2019re going to […]<\/p>\n","protected":false},"author":264026,"featured_media":296101,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[1558,1655,1566],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/09\/sun-moon.png?fit=1200%2C600&ssl=1","jetpack-related-posts":[],"featured_media_src_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/09\/sun-moon.png?fit=1024%2C512&ssl=1","_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/296088"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/264026"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=296088"}],"version-history":[{"count":7,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/296088\/revisions"}],"predecessor-version":[{"id":296116,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/296088\/revisions\/296116"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media\/296101"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=296088"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=296088"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=296088"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}