With the release of GatsbyJS 4.7, developers now have more tools and features at their disposal to create dynamic and responsive websites. One popular feature that can significantly enhance user experience is the ability to toggle between light and dark modes. This feature not only adds aesthetic appeal but also helps reduce eye strain and conserve battery power. In this blog post, we will explore how to implement a dark mode in GatsbyJS 4.7, using React context, local storage, and CSS transitions.
We will start by defining the context for our dark mode. In the RootLayout.js
file, we add a new context provider DarkModeContextProvider
. This provider is used to pass down the dark mode state and the function to toggle it to other components in the application.
import React from 'react';
import { DarkModeContextProvider } from './context/DarkModeContext';
export default function RootLayout({ children }) {
return (
<DarkModeContextProvider>
{children}
</DarkModeContextProvider>
);
}
We define two theme styles for light and dark modes in theme.js
. Each theme style includes the text and background colors, among others, as shown below:
import * as colors from './colors';
export const themeLight = {
theme: 'light',
text: colors.textDark,
background: '#fff',
cardBackground: '#0000001a',
shadow: '#0000001a',
};
export const themeDark = {
theme: 'dark',
text: '#fff',
background: colors.dark,
cardBackground: '#ffffff2f',
shadow: '#FFC836',
};
We create a new context DarkModeContext
in DarkModeContext.js
and use a state variable isDark
to keep track of whether the dark mode is enabled. The setDarkMode
function changes the isDark
state and stores the user’s preference in localStorage
.
import React, { createContext, useState, useEffect } from 'react';
import { ThemeProvider } from '@emotion/react';
import { themeDark, themeLight } from '../styles/theme';
export const DarkModeContext = createContext();
export function DarkModeContextProvider({ children }) {
const [isDark, setIsDark] = useState(false);
const setDarkMode = (darkness) => {
setIsDark(darkness);
localStorage.setItem('dark-mode', darkness ? 'dark' : 'light');
};
//...
}
In the NightDayToggle.js
file, we create a component that toggles between light and dark modes. The checked
prop determines whether the toggle is in the on (dark mode) or off (light mode) state. The changed
prop is a function that handles the user’s interaction with the toggle.
import React from 'react';
import './styles.css';
export const NightDayToggle = ({ checked, changed }) => {
return (
<div className='toggle'>
<label className='visually-hidden' htmlFor='dark-toggle'>
Toggle between dark and light mode
</label>
<input
id='dark-toggle'
className='toggle-input'
type='checkbox'
checked={checked}
onChange={changed}
/>
<div className='toggle-bg'></div>
<div className='toggle-switch'>
<div className='toggle-switch-figure'></div>
<div className='toggle-switch-figureAlt'></div>
</div>
</div>
);
};
Finally, we style the toggle by creating a style.css
file. The CSS in this file is responsible for the appearance of the toggle button and the transition between light and dark modes.
/* Credit to Jason Tyler */
.toggle,
.toggle * {
box-sizing: content-box;
}
.toggle {
position: absolute;
display: inline-block;
width: 100px;
padding: 4px;
border-radius: 40px;
transform: scale(0.4);
flex-shrink: 0;
margin-left: -20px;
margin-right: -30px;
top: 75px;
right: 10px;
}
.toggle-bg {
position: absolute;
top: -4px;
left: -4px;
width: 100%;
height: 100%;
background-color: #c0e6f6;
border-radius: 40px;
border: 4px solid #81c0d5;
transition: all 0.1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.toggle-input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid red;
border-radius: 40px;
z-index: 2;
opacity: 0;
}
.toggle-switch {
position: relative;
width: 40px;
height: 40px;
margin-left: 50px;
background-color: #f5eb42;
border: 4px solid #e4c74d;
border-radius: 50%;
transition: all 0.1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.toggle-switch-figure {
position: absolute;
bottom: -14px;
left: -50px;
display: block;
width: 80px;
height: 30px;
border: 8px solid #d4d4d2;
border-radius: 20px;
background-color: #fff;
transform: scale(0.4);
transition: all 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.toggle-switch-figure:after {
content: '';
display: block;
position: relative;
top: -65px;
right: -42px;
width: 15px;
height: 15px;
border: 8px solid #d4d4d2;
border-radius: 100%;
border-right-color: transparent;
border-bottom-color: transparent;
transform: rotateZ(70deg);
background-color: #fff;
}
.toggle-switch-figure:before {
content: '';
display: block;
position: relative;
top: -25px;
right: -10px;
width: 30px;
height: 30px;
border: 8px solid #d4d4d2;
border-radius: 100%;
border-right-color: transparent;
border-bottom-color: transparent;
transform: rotateZ(30deg);
background-color: #fff;
}
.toggle-switch-figureAlt {
content: '';
position: absolute;
top: 5px;
left: 2px;
width: 2px;
height: 2px;
background-color: #efeeda;
border-radius: 100%;
border: 4px solid #dee1c5;
box-shadow: 42px -7px 0 -3px #fcfcfc, 75px -10px 0 -3px #fcfcfc,
54px 4px 0 -4px #fcfcfc, 83px 7px 0 -2px #fcfcfc, 63px 18px 0 -4px #fcfcfc,
44px 28px 0 -2px #fcfcfc, 78px 23px 0 -3px #fcfcfc;
transition: all 0.12s cubic-bezier(0.25, 0.46, 0.45, 0.94);
transform: scale(0);
}
.toggle-switch-figureAlt:before {
content: '';
position: absolute;
top: -6px;
left: 18px;
width: 7px;
height: 7px;
background-color: #efeeda;
border-radius: 100%;
border: 4px solid #dee1c5;
}
.toggle-switch-figureAlt:after {
content: '';
position: absolute;
top: 19px;
left: 15px;
width: 2px;
height: 2px;
background-color: #efeeda;
border-radius: 100%;
border: 4px solid #dee1c5;
}
.toggle-input:checked ~ .toggle-switch {
margin-left: 0;
border-color: #dee1c5;
background-color: #fffdf2;
}
.toggle-input:checked ~ .toggle-bg {
background-color: #484848;
border-color: #202020;
}
.toggle-input:checked ~ .toggle-switch .toggle-switch-figure {
margin-left: 40px;
opacity: 0;
transform: scale(0.1);
}
.toggle-input:checked ~ .toggle-switch .toggle-switch-figureAlt {
transform: scale(1);
}
With these steps, we have successfully added a functional dark mode to our GatsbyJS 4.7 application. This not only makes the application more user-friendly but also adds a modern touch to its design. The implementation of the dark mode feature showcases how GatsbyJS 4.7, coupled with React context and local storage, can be used to enhance the user interface and experience of an application.
Written by Matt Gregg, a UI engineer who lives and works in Minneapolis, MN
Have something to say about this post? Tweet at me @itwasmattgregg