Implementing a Dark‑Mode Toggle in MUI v5 with ThemeProvider, createTheme, and useMediaQuery
Learn how to add a user‑controlled dark‑mode switch to a MUI v5 app using ThemeProvider, createTheme, and useMediaQuery. The guide covers the core code, common pitfalls, and how to verify the theme changes.
26 Oct 2025, 14:45 UTC

Why a Dark‑Mode Toggle Matters
Modern users expect a dark theme that respects their system preference or personal choice. In Material‑UI v5 the palette is the single source of truth for colors, so a toggle simply switches the palette.mode between "light" and "dark". The rest of the UI updates automatically.
Core Mechanism
Three pieces work together:
createTheme– defines the light and dark palettes.ThemeProvider– injects the current theme into the component tree.useMediaQuery– reads the user’s system preference to set an initial mode.
When the user clicks the toggle button, a state variable updates, causing ThemeProvider to re‑render with the new theme. All MUI components respond instantly.
Concrete Code Example
The following snippet shows a minimal, self‑contained implementation. Copy it into a src/App.tsx file of a Create‑React‑App or Vite project that has @mui/material installed.
import React, { useState } from 'react';
import {
ThemeProvider,
createTheme,
CssBaseline,
Button,
Box,
Typography,
useMediaQuery,
useTheme
} from '@mui/material';
// 1. Define two themes with the same palette shape.
const lightTheme = createTheme({
palette: { mode: 'light' }
});
const darkTheme = createTheme({
palette: { mode: 'dark' }
});
export default function App() {
// 2. Detect system preference on first render.
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = useState<'light' | 'dark'>(prefersDark ? 'dark' : 'light');
// 3. Switch theme based on current mode.
const theme = mode === 'light' ? lightTheme : darkTheme;
const toggle = () => setMode(prev => (prev === 'light' ? 'dark' : 'light'));
return (
{/* CssBaseline normalises CSS and applies the theme background */}
Current mode: {mode}
Toggle Dark Mode
);
}
What Happens Under the Hood?
When mode changes, React re‑renders ThemeProvider with a new theme object. MUI’s useTheme hook (used implicitly by components) pulls values from the nearest provider. The CssBaseline component injects global CSS that sets the page background and text color based on theme.palette.background.default and theme.palette.text.primary. Therefore, the entire app adapts without any manual style updates.
Verifying the Toggle Works
- Visual Check: Click the button and observe the background change from white to dark gray and text from black to light gray.
- Inspect CSS Variables: Open DevTools, select
<body>, and look for--mui-palette-background-defaultand--mui-palette-text-primary. They should update when toggling. - Console Warnings: Ensure no warnings about missing
ThemeProvideror invalidpalette.modekeys appear.
Common Mistakes and How to Avoid Them
- Missing
CssBaseline: Without it, the body background stays at the browser default, making the dark mode look broken. - Wrapping Only Part of the Tree: If
ThemeProvideris inside a component that doesn’t contain all MUI elements, those outside won’t receive the theme. - Using
palette.typeInstead ofmode: MUI v5 removedtype. Projects upgraded from v4 may break if they still reference it. - Custom CSS Not Using Theme Variables: Styles written in plain CSS or styled‑components without referencing the theme will not update automatically. Use
useThemeor CSS variables to keep them in sync. - Forgetting to Persist the Choice: The example stores mode only in memory. For a real app, persist the preference in
localStorageor a backend so the choice survives refreshes.
Limitations and Edge Cases
- Server‑Side Rendering (SSR): The
useMediaQueryhook relies onwindow, so on the server it will default tofalse. Wrap the component inuseMediaQuerywithssrMatchMediaor set a default mode explicitly. - Performance of Theme Re‑creation: Creating a new theme on every toggle can be expensive for large applications. Store the themes outside the component or memoize them with
useMemo. - Non‑MUI Elements: Images, videos, and other third‑party components won’t automatically adjust. They may need manual CSS adjustments.
Extending the Example
Here’s how you could persist the user’s choice:
const [mode, setMode] = useState<'light' | 'dark'>(localStorage.getItem('theme') as any ?? (prefersDark ? 'dark' : 'light'));
const toggle = () => {
const next = mode === 'light' ? 'dark' : 'light';
setMode(next);
localStorage.setItem('theme', next);
};
Conclusion
Switching themes in MUI v5 is straightforward once you understand the three core concepts: createTheme, ThemeProvider, and useMediaQuery. With the example above, a working dark‑mode toggle can be added to any MUI project in just a few lines. Remember to wrap the entire tree, include CssBaseline, and avoid the old palette.type key. Verify by inspecting CSS variables and ensuring no console errors. Happy theming!
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.