Customizing Material‑UI Themes: A Practical Guide to Primary Color, Typography, and Dark Mode
Learn how to set a custom primary color, typography, and dark mode in a Material‑UI app using ThemeProvider. Follow a step‑by‑step guide, verify with devtools, and recover from common issues.
09 Oct 2026, 16:24 UTC

Desired Outcome
Apply a consistent, custom theme across a React app using Material‑UI (MUI). The theme should set a primary color, a global typography stack, and optionally toggle dark mode on user action.
Prerequisites
- React 18+ application (e.g., created with Create React App or Vite).
- Material‑UI v5 (MUI) installed:
npm i @mui/material @emotion/react @emotion/styled. - Basic understanding of React components and state.
Step‑by‑Step Procedure
Import ThemeProvider and createTheme
Place the following imports at the top of your
index.js(ormain.jsx) file.import { ThemeProvider, createTheme } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline';Define the base theme object
Use
createThemeto merge defaults with your overrides. The key points are thepalettefor colors andtypographyfor fonts.const baseTheme = createTheme({ palette: { primary: { main: '#1976d2', // Custom blue }, }, typography: { fontFamily: 'Roboto, Arial, sans-serif', }, });At this stage the theme is immutable; any change requires creating a new object.
Wrap the root component with ThemeProvider
Wrap
<App/>inside<ThemeProvider>and add<CssBaseline/>to normalize styles.ReactDOM.createRoot(document.getElementById('root')).render( );Place this code in
index.jsso the provider is at the top of the component tree.Make the theme dynamic – toggle dark mode
To switch between light and dark palettes, keep the palette mode in state and memoize the theme so re‑renders are minimized.
import { useState, useMemo } from 'react'; function Root() { const [mode, setMode] = useState('light'); const theme = useMemo(() => createTheme({ palette: { mode, primary: { main: '#1976d2' }, }, typography: { fontFamily: 'Roboto, Arial, sans-serif' }, }), [mode]); return ( setMode(m => m === 'light' ? 'dark' : 'light')} /> ); }In
App, a simple button can triggertoggleDarkto switch modes.Use theme values in components
Components automatically consume the theme via the
sxprop or thestyled()helper. For example:import Button from '@mui/material/Button'; function ThemedButton() { return ( <Button sx={{ color: 'primary.main', fontSize: 18 }}> Click Me </Button> ); }Here
primary.mainpulls the custom blue from the theme.Verify the theme is applied
- Open the browser devtools, inspect a
<Button/>, and confirm its computed color matches#1976d2or the dark mode equivalent. - Render a minimal component inside the provider with a distinct palette color and check the computed style in the console.
- Use React DevTools to ensure
ThemeProvideris the ancestor of every component that should receive the theme.
- Open the browser devtools, inspect a
Recovery & Troubleshooting
- If components do not reflect the custom color, verify that
ThemeProviderwraps the entire tree and that the import path is@mui/material/styles. - Check that the theme object is not mutated after creation; any direct mutation will be ignored.
- When toggling dark mode, ensure the
modestate is included in theuseMemodependency array to rebuild the theme only when needed. - If stale styles persist after a change, clear the browser cache or use
React.StrictModeto force a fresh render.
- If components do not reflect the custom color, verify that
Limitations and Best Practices
- Do not mix legacy styling APIs (
makeStyles,withStyles) with thesxprop; choose one to avoid precedence conflicts. - Theme overrides that reference non‑existent keys (e.g.,
palette.primary.maininstead ofpalette.primary) are silently ignored. - Dynamic theme changes should be memoized; otherwise every state update triggers a full re‑render and can hurt performance.
Concrete Example: Dark Mode Toggle
// index.js
import React, { useState, useMemo } from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider, createTheme, CssBaseline, Button } from '@mui/material';
function App({ toggleDark }) {
return (
<div>
<Button variant="contained" onClick={toggleDark}>
Toggle Dark Mode
</Button>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Root />
);
function Root() {
const [mode, setMode] = useState('light');
const theme = useMemo(
() => createTheme({
palette: { mode, primary: { main: '#1976d2' } },
typography: { fontFamily: 'Roboto, Arial, sans-serif' },
}),
[mode]
);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<App toggleDark={() => setMode(m => (m === 'light' ? 'dark' : 'light'))} />
</ThemeProvider>
);
}
Running this code will show a button that toggles between light and dark palettes, with the primary color consistently applied to all MUI components.
Conclusion
By defining a single ThemeProvider at the root, you can guarantee that all Material‑UI components share the same primary color, typography, and optional dark mode. Remember to keep the theme immutable, memoize dynamic changes, and verify via devtools to avoid common pitfalls.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.