Dark mode has become a popular feature in modern web applications, offering users the ability to switch between light and dark color schemes. This guide will walk you through implementing a dark mode toggle in a React application, covering various aspects and best practices.
First, let's create a new React project using Create React App:
npx create-react-app dark-mode-toggle
cd dark-mode-toggle
npm start
To manage the dark mode state across your application, we'll use React Context:
// src/contexts/DarkModeContext.js
import React, { createContext, useState, useContext, useEffect } from 'react';
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children }) => {
const [isDarkMode, setIsDarkMode] = useState(false);
const toggleDarkMode = () => {
setIsDarkMode(prevMode => !prevMode);
};
useEffect(() => {
const isDark = localStorage.getItem('darkMode') === 'true';
setIsDarkMode(isDark);
}, []);
useEffect(() => {
localStorage.setItem('darkMode', isDarkMode);
}, [isDarkMode]);
return (
<DarkModeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
{children}
</DarkModeContext.Provider>
);
};
export const useDarkMode = () => useContext(DarkModeContext);
Now, let's create a toggle component:
// src/components/DarkModeToggle.js
import React from 'react';
import { useDarkMode } from '../contexts/DarkModeContext';
const DarkModeToggle = () => {
const { isDarkMode, toggleDarkMode } = useDarkMode();
return (
<button onClick={toggleDarkMode}>
{isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</button>
);
};
export default DarkModeToggle;
To apply dark mode styles, we can use CSS variables:
/* src/styles/darkMode.css */
:root {
--background-color: #ffffff;
--text-color: #000000;
}
[data-theme='dark'] {
--background-color: #1a1a1a;
--text-color: #ffffff;
}
body {
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
Update your App component to apply the theme:
// src/App.js
import React from 'react';
import { useDarkMode } from './contexts/DarkModeContext';
import DarkModeToggle from './components/DarkModeToggle';
import './styles/darkMode.css';
function App() {
const { isDarkMode } = useDarkMode();
return (
<div data-theme={isDarkMode ? 'dark' : 'light'}>
<h1>Dark Mode Toggle Example</h1>
<DarkModeToggle />
{/* Your app content */}
</div>
);
}
export default App;
We've already implemented persistence in our DarkModeContext using localStorage. This ensures that the user's preference is remembered across sessions.
To respect the user's system preferences, we can use the prefers-color-scheme
media query:
// Update DarkModeContext.js
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => setIsDarkMode(mediaQuery.matches);
mediaQuery.addListener(handleChange);
setIsDarkMode(mediaQuery.matches);
return () => mediaQuery.removeListener(handleChange);
}, []);
To optimize performance, consider using the useCallback
hook for the toggleDarkMode
function:
const toggleDarkMode = useCallback(() => {
setIsDarkMode(prevMode => !prevMode);
}, []);
Ensure your toggle component is keyboard accessible and has proper ARIA attributes:
// Update DarkModeToggle.js
return (
<button
onClick={toggleDarkMode}
aria-pressed={isDarkMode}
aria-label="Toggle dark mode"
>
{isDarkMode ? '🌙' : '☀️'}
</button>
);
Here's an example of how to test your dark mode implementation:
// src/__tests__/DarkModeToggle.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { DarkModeProvider } from '../contexts/DarkModeContext';
import DarkModeToggle from '../components/DarkModeToggle';
test('toggles dark mode when clicked', () => {
const { getByRole } = render(
<DarkModeProvider>
<DarkModeToggle />
</DarkModeProvider>
);
const toggle = getByRole('button');
expect(toggle).toHaveTextContent('Switch to Dark Mode');
fireEvent.click(toggle);
expect(toggle).toHaveTextContent('Switch to Light Mode');
});
You can create themed components that adapt to the current mode:
// src/components/ThemedButton.js
import React from 'react';
import styled from 'styled-components';
import { useDarkMode } from '../contexts/DarkModeContext';
const StyledButton = styled.button`
background-color: e=>e.isDarkMode?"#333":"#f0f0f0";
color: e=>e.isDarkMode?"#fff":"#000";
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
`;
const ThemedButton = ({ children, ...props }) => {
const { isDarkMode } = useDarkMode();
return <StyledButton isDarkMode={isDarkMode} {...props}>{children}</StyledButton>;
};
export default ThemedButton;
To create smooth transitions between modes, you can use CSS transitions:
/* Update darkMode.css */
body {
transition: background-color 0.5s ease, color 0.5s ease;
}
[data-theme='dark'] .animate-dark-mode {
animation: toDark 0.5s ease forwards;
}
[data-theme='light'] .animate-dark-mode {
animation: toLight 0.5s ease forwards;
}
@keyframes toDark {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes toLight {
from { opacity: 0; }
to { opacity: 1; }
}
For more complex theming, you can create a theme object and use it with styled-components:
// src/themes/index.js
export const lightTheme = {
body: '#fff',
text: '#000',
primary: '#0077ff',
secondary: '#ff4081',
};
export const darkTheme = {
body: '#1a1a1a',
text: '#fff',
primary: '#00aaff',
secondary: '#ff6b9b',
};
// src/App.js
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { useDarkMode } from './contexts/DarkModeContext';
import { lightTheme, darkTheme } from './themes';
function App() {
const { isDarkMode } = useDarkMode();
const theme = isDarkMode ? darkTheme : lightTheme;
return (
<ThemeProvider theme={theme}>
{/* Your app content */}
</ThemeProvider>
);
}
2024 © All rights reserved - buraxta.com