Custom Material UI Palette Theme Not Being Applied in React with TypeScript
Technical Stack
- TypeScript
- React
- Material UI v5
- Vite
Problem Description
I’m trying to apply a custom palette theme in my Material UI application, but the custom colors are not being applied. Despite following the documentation and checking various resources, I can’t identify what’s wrong with my implementation.
File Structure
App.tsx
: Main page componentglobal_theme.tsx
: Contains light/dark mode configuration, CSS variables, and breakpoint valuestheme.tsx
: Contains the actual palette variables I’m trying to use
Code Implementation
App.tsx
import Typography from '@mui/material/Typography';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import { Box, CssBaseline, Link } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import { ThemeProvider as Emotion10ThemeProvider } from '@emotion/react';
import { StyledEngineProvider } from "@mui/material";
import './App.css'
import homepageTheme from './theme';
function App() {
const homeTheme = homepageTheme;
console.log(homeTheme);
return (
<div>
<CssBaseline />
<ThemeProvider theme={homeTheme}>
<Emotion10ThemeProvider theme={homeTheme}>
<StyledEngineProvider>
<Box id="homepage" sx={{ backgroundColor: homeTheme.palette.background.default }}>
<Card sx={{ p: 5, mb: 10, backgroundColor: homeTheme.palette.primary.light }}>
<CardContent>
<Typography id="page-title" sx={{ fontSize: 52, color: homeTheme.palette.primary.contrastText }}>
Zach M
</Typography>
</CardContent>
</Card>
</Box>
</StyledEngineProvider>
</Emotion10ThemeProvider>
</ThemeProvider>
</div>
)
}
export default App
global_theme.tsx
import { createTheme, type ThemeOptions } from "@mui/material/styles";
declare module '@mui/material/styles' {
interface BreakpointOverrides {
mobile: true;
tablet: true;
laptop: true;
desktop: true;
xs: false;
sm: false;
md: false;
lg: false;
xl: false;
}
}
const globalTheme = createTheme({
breakpoints: {
values: {
mobile: 0,
tablet: 640,
laptop: 1024,
desktop: 1200,
},
},
colorSchemes: {
light: true,
dark: true,
},
cssVariables: true,
});
export default globalTheme;
theme.tsx
import { createTheme } from '@mui/material/styles';
import { grey, blue, indigo, cyan, red, purple } from '@mui/material/colors';
import globalTheme from './global_theme';
declare module '@mui/material/styles' {
interface Palette {
custom: Palette['primary'];
}
interface PaletteOptions {
custom?: PaletteOptions['primary'];
}
}
const homepageTheme = createTheme({
...globalTheme,
colorSchemes: {
light: {
palette: {
background: {
default: purple[100],
},
primary: {
main: '#4d858aff',
dark: '#203840ff',
light: red[500],
contrastText: '#0b1d23ff',
},
secondary: {
main: '#c3e8ed',
light: cyan[50],
dark: '#afd6db',
contrastText: grey[800],
}
},
},
dark: {
palette: {
background: {
default: blue[900]
},
primary: {
main: blue[900],
contrastText: blue[200],
},
secondary: {
main: indigo[500],
}
},
},
},
});
export default homepageTheme;
Console Output
When I log the theme object, I can see that the breakpoints are correctly configured, but the custom color themes are not being applied. The console output shows the default Material UI colors instead of my custom palette.
Troubleshooting Steps Already Taken
- Checked for package updates (npm)
- Verified all imports are correct
- Confirmed that
theme.tsx
is usingcreateTheme()
correctly - Verified that palette variables are being accessed correctly
- Added
<CssBaseline />
component - Implemented
<ThemeProvider>
and<StyledEngineProvider>
- Tried both
bgcolor
andbackgroundColor
properties - Tested both
sx={{}}
andstyle=
attributes
Expected Behavior
The custom colors defined in the theme should be applied to the components. For example:
- The background should be purple[100] (light purple)
- The card background should be red[500] (red)
- The text should be #0b1d23ff (dark blue)
Actual Behavior
The default Material UI colors are being applied instead of the custom colors.
Additional Information
The project was set up using Vite, which resolved package management issues, but I’m wondering if this might be causing the theme configuration problem.
I’ve reviewed multiple StackOverflow posts and the official Material UI documentation, but haven’t been able to resolve this issue. The code compiles without errors or warnings.
Custom Material UI Palette Theme Not Being Applied in React with TypeScript
Brief Answer: Your custom Material UI palette theme isn’t being applied due to incorrect theme extension structure and potential conflicts between your global and custom theme configurations. The main issues are in how you’re extending the base theme and how you’re structuring the color schemes in Material UI v5.
Contents
- Understanding the Theme Extension Problem
- Fixing the Global Theme Configuration
- Correcting the Custom Theme Implementation
- Simplifying the Theme Provider Setup
- Alternative Approaches for Theme Configuration
- Verifying the Theme Application
- Conclusion and Best Practices
Understanding the Theme Extension Problem
The core issue in your implementation is how you’re extending themes in Material UI v5. Material UI v5 introduced significant changes to theming, particularly with the new color scheme API. When you create a theme in global_theme.tsx
and then try to extend it in theme.tsx
, you’re not properly merging the configurations.
In Material UI v5, themes should be created using the
createTheme()
function with proper extension syntax. Simply spreading the global theme isn’t enough when working with color schemes.
The console output shows that your breakpoints are correctly configured but the custom colors aren’t applied, which indicates that while the theme structure is partially working, the palette configuration isn’t being properly merged or applied.
Fixing the Global Theme Configuration
Your global_theme.tsx
needs to be restructured to properly define theme options rather than a complete theme:
import { createTheme, type ThemeOptions } from "@mui/material/styles";
declare module '@mui/material/styles' {
interface BreakpointOverrides {
mobile: true;
tablet: true;
laptop: true;
desktop: true;
xs: false;
sm: false;
md: false;
lg: false;
xl: false;
}
}
export const globalThemeOptions: ThemeOptions = {
breakpoints: {
values: {
mobile: 0,
tablet: 640,
laptop: 1024,
desktop: 1200,
},
},
colorSchemes: {
light: true,
dark: true,
},
cssVariables: true,
};
// Create a base theme that can be extended
export const baseTheme = createTheme(globalThemeOptions);
This approach separates theme options from the complete theme, making it easier to extend and modify in other files.
Correcting the Custom Theme Implementation
Your theme.tsx
file needs to properly extend the base theme using the correct syntax for Material UI v5’s color scheme API:
import { createTheme } from '@mui/material/styles';
import { grey, blue, indigo, cyan, red, purple } from '@mui/material/colors';
import { baseTheme } from './global_theme';
declare module '@mui/material/styles' {
interface Palette {
custom: Palette['primary'];
}
interface PaletteOptions {
custom?: PaletteOptions['primary'];
}
}
const homepageTheme = createTheme({
...baseTheme,
colorSchemes: {
light: {
palette: {
background: {
default: purple[100],
},
primary: {
main: '#4d858aff',
dark: '#203840ff',
light: red[500],
contrastText: '#0b1d23ff',
},
secondary: {
main: '#c3e8ed',
light: cyan[50],
dark: '#afd6db',
contrastText: grey[800],
},
custom: {
main: '#ff0000',
light: '#ff6666',
dark: '#cc0000',
contrastText: '#ffffff',
}
},
},
dark: {
palette: {
background: {
default: blue[900]
},
primary: {
main: blue[900],
contrastText: blue[200],
},
secondary: {
main: indigo[500],
},
custom: {
main: '#00ff00',
light: '#66ff66',
dark: '#00cc00',
contrastText: '#000000',
}
},
},
},
});
export default homepageTheme;
Key changes:
- Properly extending
baseTheme
using the spread operator - Correctly structuring the
colorSchemes
with both light and dark variants - Adding the custom palette color to both schemes
Simplifying the Theme Provider Setup
Your App.tsx
has multiple theme providers that could be causing conflicts. Simplify it like this:
import Typography from '@mui/material/Typography';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import { Box, CssBaseline } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import { StyledEngineProvider } from "@mui/material";
import './App.css'
import homepageTheme from './theme';
function App() {
return (
<div>
<CssBaseline />
<StyledEngineProvider injectFirst>
<ThemeProvider theme={homepageTheme}>
<Box id="homepage" sx={{ backgroundColor: 'background.default' }}>
<Card sx={{ p: 5, mb: 10, backgroundColor: 'primary.light' }}>
<CardContent>
<Typography id="page-title" sx={{ fontSize: 52, color: 'primary.contrastText' }}>
Zach M
</Typography>
</CardContent>
</Card>
</Box>
</ThemeProvider>
</StyledEngineProvider>
</div>
)
}
export default App
Changes made:
- Removed the redundant
Emotion10ThemeProvider
as it’s not needed when using Material UI’sThemeProvider
- Simplified the component references to use theme keys directly (e.g.,
'background.default'
instead ofhomeTheme.palette.background.default
) - Kept
StyledEngineProvider
withinjectFirst
which is important for proper styling precedence
Alternative Approaches for Theme Configuration
If the above approach doesn’t work, consider these alternatives:
1. Using Simple Palette Extension
import { createTheme } from '@mui/material/styles';
import { grey, blue, indigo, cyan, red, purple } from '@mui/material/colors';
import { baseTheme } from './global_theme';
declare module '@mui/material/styles' {
interface Palette {
custom: Palette['primary'];
}
interface PaletteOptions {
custom?: PaletteOptions['primary'];
}
}
const homepageTheme = createTheme({
...baseTheme,
palette: {
primary: {
main: '#4d858aff',
dark: '#203840ff',
light: red[500],
contrastText: '#0b1d23ff',
},
secondary: {
main: '#c3e8ed',
light: cyan[50],
dark: '#afd6db',
contrastText: grey[800],
},
background: {
default: purple[100],
},
custom: {
main: '#ff0000',
light: '#ff6666',
dark: '#cc0000',
contrastText: '#ffffff',
},
},
});
export default homepageTheme;
2. Using a Theme Factory
import { createTheme } from '@mui/material/styles';
import { grey, blue, indigo, cyan, red, purple } from '@mui/material/colors';
import { baseTheme } from './global_theme';
declare module '@mui/material/styles' {
interface Palette {
custom: Palette['primary'];
}
interface PaletteOptions {
custom?: PaletteOptions['primary'];
}
}
const createHomepageTheme = (mode: 'light' | 'dark' = 'light') =>
createTheme({
...baseTheme,
palette: {
mode,
primary: {
main: mode === 'light' ? '#4d858aff' : blue[900],
dark: '#203840ff',
light: mode === 'light' ? red[500] : blue[200],
contrastText: mode === 'light' ? '#0b1d23ff' : blue[200],
},
secondary: {
main: mode === 'light' ? '#c3e8ed' : indigo[500],
light: cyan[50],
dark: '#afd6db',
contrastText: grey[800],
},
background: {
default: mode === 'light' ? purple[100] : blue[900],
},
custom: {
main: mode === 'light' ? '#ff0000' : '#00ff00',
light: mode === 'light' ? '#ff6666' : '#66ff66',
dark: mode === 'light' ? '#cc0000' : '#00cc00',
contrastText: mode === 'light' ? '#ffffff' : '#000000',
},
},
});
export default createHomepageTheme('light');
export const darkHomepageTheme = createHomepageTheme('dark');
Verifying the Theme Application
After implementing the fixes, you can verify that your theme is applied correctly:
- Check the console output: Modify your
App.tsx
to log specific palette values:
function App() {
return (
<ThemeProvider theme={homepageTheme}>
<Box>
{/* Your components */}
{console.log('Primary main:', homepageTheme.palette.primary.main)}
{console.log('Background default:', homepageTheme.palette.background.default)}
</Box>
</ThemeProvider>
)
}
-
Use browser dev tools: Inspect the CSS variables applied to the root element. With
cssVariables: true
enabled, you should see custom CSS variables like:--mui-palette-primary-main: #4d858aff; --mui-palette-background-default: #e1bee7;
-
Test with direct styling: Add a temporary style to verify colors are working:
<Box sx={{
backgroundColor: theme => theme.palette.primary.main,
color: theme => theme.palette.primary.contrastText,
p: 2
}}>
Test Box
</Box>
Conclusion and Best Practices
Key Takeaways:
- Material UI v5 uses a different theme extension approach than v4
- The
colorSchemes
API has a specific structure that must be followed - Multiple theme providers can cause conflicts
- CSS variables are enabled by default when using
cssVariables: true
Recommendations:
- Start simple: Begin with a basic theme and gradually add complexity
- Use TypeScript modules: Always declare palette customizations in module declarations
- Test incrementally: Verify each change to identify where things break
- Consider theme variants: Create separate light and dark themes rather than trying to merge them
- Use the dev tools: Material UI provides excellent dev tools for theme debugging
By following these guidelines, you should be able to successfully apply your custom palette theme to your Material UI application. Remember that Material UI v5’s theming system is powerful but requires careful attention to the new APIs and structure.
For further troubleshooting, consider using the Material UI Theme Creator tool to experiment with theme configurations before implementing them in your code.