Add dark mode to a React with TypeScript and styled-components app
In this post we'll see how to add dark mode to an application made with React, TypeScript, and styled-components, and store the choice into local storage so it's saved for following visits.
Define Global Styles
Firstly, we'll define some global styles using styled-components's createGlobalStyle
function, where we'll use CSS variables to define the base colors, and declare each element color conditionally depending on the theme prop we'll pass later on:
import {createGlobalStyle, css} from "styled-components"
interface Props {
theme: string
}
export const GlobalStyles = createGlobalStyle(
(props: Props) => css`
:root {
--color-dark: hsl(0, 0%, 10%);
--color-light: hsl(0, 0%, 95%);
}
body {
background-color: ${props.theme === "light" ? "var(--color-light)" : "var(--color-dark)"};
color: ${props.theme === "light" ? "var(--color-dark)" : "var(--color-light)"};
}
`,
)
Create A Theme Context
Then, using a modified version of useHooks's useLocalStorage Hook to store the chosen theme in the browser's local storage, we'll create a global state with React's Context API, to pass the theme variable an the updater function to the Provider and GlobalStyles components that will wrap the whole application:
import React, {createContext, useState} from "react"
import {GlobalStyles} from "../components"
function useLocalStorage(key: string, initialValue: string): [string, Function] {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.log(error)
return initialValue
}
})
const setValue = (valueToStore: string): void => {
try {
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.log(error)
}
}
return [storedValue, setValue]
}
interface ContextProps {
theme: string
setTheme: Function
}
export const ThemeContext = createContext<ContextProps>({
theme: "",
setTheme: () => null,
})
interface Props {
children: React.ReactNode
}
export function ThemeProvider(props: Props): JSX.Element {
const [theme, setTheme] = useLocalStorage("theme", "light")
return (
<ThemeContext.Provider value={{theme, setTheme}}>
<GlobalStyles theme={theme} />
{props.children}
</ThemeContext.Provider>
)
}
Add A Theme Toggle Button
Finally, we'll create a button than can change the theme variable and the elements styles will update accordingly:
import React, { useContext } from 'react';
import { ThemeContext } from '../contexts';
export function ThemeButton(): JSX.Element {
const { theme, setTheme } = useContext(ThemeContext);
function swapTheme {
if (theme === 'light') {
setTheme('dark');
} else {
setTheme('light');
}
};
return (
<button onClick={swapTheme}>
{theme === 'light' ? 'Change to dark mode' : 'Change to light mode'}
</button>
);
}
If you're using dark mode, do you like the code blocks's theme? I have it available for VS Code, feel free to check it.