1. Quick Start
Material UI is a comprehensive React component library implementing Google's Material Design. It provides 86 production-ready components, a powerful theming system, and the sx prop for rapid styling — all with full TypeScript support.
Key features:
- 86 pre-built React components (buttons, dialogs, tables, navigation, and more)
- Fully customizable theme system with dark mode and CSS variables support
- The
sxprop for inline, theme-aware styling on any MUI component - Slots and slotProps API for deep component customization
- Polymorphic
componentprop for HTML element overrides - 60+ locale translations
- Tree-shakeable — import only what you use
- React 17, 18, and 19 compatible
Prerequisites: Node.js >= 22.18.0, React >= 17.0.0
Install and render your first component:
npm install @mui/material @emotion/react @emotion/styled
import * as React from 'react';
import Button from '@mui/material/Button';
export default function App() {
return <Button variant="contained">Hello World</Button>;
}
Tip
For the fastest setup with a framework, use one of the official example projects. MUI provides starter templates for Next.js, Vite, Gatsby, Remix, React Router, and more.
What to try next:
- Add a theme — see Configuration below
- Explore components by category — see Core Usage
- Build a complete page — see Common Tasks
2. Installation
System Requirements
| Requirement | Minimum Version |
|---|---|
| Node.js | >= 22.18.0 |
| React | >= 17.0.0 |
| Package manager | npm, yarn, or pnpm |
Install Methods
npm
npm install @mui/material @emotion/react @emotion/styled
yarn
yarn add @mui/material @emotion/react @emotion/styled
pnpm
pnpm add @mui/material @emotion/react @emotion/styled
CDN
<script crossorigin src="https://unpkg.com/@mui/material@latest/umd/material-ui.production.min.js"></script>
Important
Emotion (
@emotion/reactand@emotion/styled) is a required peer dependency. MUI does not bundle its own styling engine — you must install one. If you prefer styled-components, see the styled-components section below.
Optional Packages
| Package | Command | Purpose |
|---|---|---|
@mui/icons-material |
npm install @mui/icons-material |
2,100+ Material Design icons in 5 style variants |
@mui/lab |
npm install @mui/lab |
Experimental components (LoadingButton, Masonry, Timeline, etc.) |
@mui/codemod |
npx @mui/codemod <transform> <path> |
Automated migration between MUI versions |
@mui/material-nextjs |
npm install @mui/material-nextjs |
Next.js SSR integration helpers |
@mui/envinfo |
npx @mui/envinfo |
Print environment diagnostics |
Using styled-components Instead of Emotion
MUI supports styled-components as an alternative styling engine:
npm install @mui/material @mui/styled-engine-sc styled-components
Configure your bundler to alias @mui/styled-engine to @mui/styled-engine-sc. For example, in webpack:
// webpack.config.js
module.exports = {
resolve: {
alias: {
'@mui/styled-engine': '@mui/styled-engine-sc',
},
},
};
Roboto Font
Material UI uses the Roboto font by default. Install it via npm or CDN:
npm install @fontsource/roboto
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
Or via CDN link in your HTML:
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" />
Material Icons Font
If using the Icon component (font-based icons), add the Material Icons stylesheet:
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
Tip
Prefer
@mui/icons-material(SVG icons) over the font-basedIconcomponent for better tree-shaking and smaller bundles.
Post-Install Verification
npx @mui/envinfo
System:
OS: macOS 14.0
Node: 22.18.0
Binaries:
npm: 10.x
Packages:
@mui/material: 9.0.0
@emotion/react: 11.x
@emotion/styled: 11.x
react: 19.2.4
Framework-Specific Setup
Next.js (App Router)
npm install @mui/material @emotion/react @emotion/styled @mui/material-nextjs
// app/layout.tsx
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import theme from './theme';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AppRouterCacheProvider>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}
Next.js (Pages Router)
// pages/_app.tsx
import { CacheProvider } from '@emotion/react';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import createEmotionCache from '../src/createEmotionCache';
import theme from '../src/theme';
const clientSideEmotionCache = createEmotionCache();
export default function MyApp({ Component, pageProps, emotionCache = clientSideEmotionCache }) {
return (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
Vite
npm install @mui/material @emotion/react @emotion/styled
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import theme from './theme';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>,
);
Express SSR
Server-side rendering with Express requires Emotion's server-side extraction:
npm install @mui/material @emotion/react @emotion/styled @emotion/server
import { renderToString } from 'react-dom/server';
import createEmotionServer from '@emotion/server/create-instance';
import createEmotionCache from './createEmotionCache';
const cache = createEmotionCache();
const { extractCriticalToChunks, constructStyleTagsFromChunks } = createEmotionServer(cache);
const html = renderToString(<App />);
const emotionChunks = extractCriticalToChunks(html);
const emotionCss = constructStyleTagsFromChunks(emotionChunks);
Install Troubleshooting
Error: Module not found: Can't resolve '@emotion/react'
Cause: Emotion peer dependencies not installed.
Solution:
- Install the required packages:
npm install @emotion/react @emotion/styled - Verify both are in your
package.json
Prevention: Always install @mui/material with both Emotion packages.
Error: Invalid hook call when using MUI components
Cause: Multiple React instances in the bundle, or React version mismatch.
Solution:
- Check for duplicate React:
npm ls react - Ensure only one React version is resolved
- If using a monorepo, deduplicate React with package manager resolutions
Error: TypeScript fails with exactOptionalPropertyTypes
Cause: Using sx={visuallyHidden} with TypeScript's exactOptionalPropertyTypes enabled (GitHub issue #42694).
Solution: Cast the value:
import { visuallyHidden } from '@mui/utils';
<Box sx={visuallyHidden as any} />
Build warning: bundle size too large
Cause: Importing from the package root instead of specific paths.
Solution: Use path imports for smaller bundles:
// Preferred (tree-shakeable)
import Button from '@mui/material/Button';
// Also works but depends on bundler tree-shaking
import { Button } from '@mui/material';
3. Configuration
Creating a Theme
Create a custom theme using createTheme() and wrap your app with ThemeProvider:
import { createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: { fontSize: '2.5rem' },
},
shape: {
borderRadius: 8,
},
spacing: 8, // base spacing unit in px
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* Your app */}
</ThemeProvider>
);
}
Note
CssBaselineresets browser default styles (similar to normalize.css). Place it insideThemeProviderto apply theme-aware resets. UseScopedCssBaselineto reset only a subtree.
Dark Mode
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
For automatic system-preference detection with CSS variables:
import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
function App() {
return (
<CssVarsProvider>
<InitColorSchemeScript />
{/* Your app */}
</CssVarsProvider>
);
}
Breakpoints
Default breakpoints:
| Breakpoint | Min Width |
|---|---|
xs |
0px |
sm |
600px |
md |
900px |
lg |
1200px |
xl |
1536px |
Override in your theme:
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
},
},
});
Spacing
The spacing function maps a multiplier to a CSS value. Default: 8px base.
theme.spacing(2); // '16px'
theme.spacing(1, 2); // '8px 16px'
Component Default Props
Override default props globally for any component:
const theme = createTheme({
components: {
MuiButtonBase: {
defaultProps: {
disableRipple: true, // disable ripple globally
},
},
MuiTextField: {
defaultProps: {
variant: 'filled',
size: 'small',
},
},
},
});
Component Style Overrides
Override styles globally via the theme:
const theme = createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
textTransform: 'none', // disable uppercase
borderRadius: 20,
},
containedPrimary: {
boxShadow: 'none',
},
},
},
},
});
Tip
For the complete theme configuration reference including all palette options, typography variants, and component tokens, see the Admin Guide.
4. Core Usage
The sx Prop
Every MUI component accepts the sx prop for one-off, theme-aware styling. It supports all CSS properties plus shorthand aliases:
<Box
sx={{
p: 2, // padding: theme.spacing(2) = '16px'
m: 1, // margin: '8px'
bgcolor: 'primary.main', // background-color from theme palette
color: 'white',
borderRadius: 1, // theme.shape.borderRadius * 1
display: 'flex',
gap: 2,
'&:hover': { // nested selectors work
bgcolor: 'primary.dark',
},
}}
>
Styled Box
</Box>
Spacing shorthands:
| Shorthand | CSS Property |
|---|---|
m, mt, mr, mb, ml, mx, my |
margin and variants |
p, pt, pr, pb, pl, px, py |
padding and variants |
Palette shorthands:
| Shorthand | CSS Property |
|---|---|
bgcolor |
background-color |
color |
color |
Other shorthands: sx also maps properties for borders (border, borderColor, borderRadius), display, flexbox, grid, positions, shadows, sizing (width, height, minWidth, maxWidth, minHeight, maxHeight), and typography (fontFamily, fontSize, fontWeight, letterSpacing, lineHeight, textAlign).
Responsive values:
<Box
sx={{
width: {
xs: '100%', // 0px+
sm: '50%', // 600px+
md: 300, // 900px+ (number = px)
},
}}
/>
Warning
The
sxprop only works on MUI components. It does not work on plain HTML elements like<div>. Wrap plain elements with<Box>if you needsxsupport.
Component Categories
Layout Components
| Component | Purpose |
|---|---|
Box |
Generic container — the <div> of MUI (supports sx and component prop) |
Container |
Centered, max-width container for page content |
Grid |
Responsive grid layout |
Stack |
One-dimensional layout (vertical or horizontal) with spacing |
import Grid from '@mui/material/Grid';
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>
<Item>Left column</Item>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Item>Right column</Item>
</Grid>
</Grid>
Input Components
| Component | Purpose |
|---|---|
TextField |
Text input with label, helper text, and validation states |
Select |
Dropdown selection |
Autocomplete |
Combo box with filtering, free text, multiple selection |
Checkbox |
Binary toggle with indeterminate state |
Radio / RadioGroup |
Single selection from a group |
Switch |
Toggle switch |
Slider |
Range slider (continuous or discrete) |
Rating |
Star rating input |
Button |
Standard button (text, contained, outlined variants) |
IconButton |
Icon-only button |
Fab |
Floating action button |
ToggleButton / ToggleButtonGroup |
Segmented button group |
ButtonGroup |
Grouped buttons with shared styling |
import TextField from '@mui/material/TextField';
<TextField
label="Email"
variant="outlined"
type="email"
required
error={!isValid}
helperText={!isValid ? 'Please enter a valid email' : ''}
fullWidth
/>
Note
TextFieldis a convenience wrapper combiningFormControl,InputLabel,Input(orOutlinedInput/FilledInput), andFormHelperText. For maximum control, compose these lower-level components directly.
Data Display Components
| Component | Purpose |
|---|---|
Typography |
Text rendering with semantic variants (h1-h6, body1, body2, subtitle1, etc.) |
Table / TableHead / TableBody / TableRow / TableCell |
Data tables |
List / ListItem / ListItemText / ListItemIcon |
Vertical lists |
Avatar / AvatarGroup |
Profile pictures or initials |
Badge |
Notification badge overlay |
Chip |
Compact element for tags, filters, actions |
Divider |
Horizontal or vertical separator |
Tooltip |
Hover/focus information popup |
Icon / SvgIcon |
Icon rendering (font-based or SVG) |
ImageList / ImageListItem / ImageListItemBar |
Image grid layout |
import Typography from '@mui/material/Typography';
<Typography variant="h4" component="h1" gutterBottom>
Page Title
</Typography>
<Typography variant="body1" color="text.secondary">
Supporting text content.
</Typography>
Navigation Components
| Component | Purpose |
|---|---|
AppBar / Toolbar |
Top application bar |
Drawer / SwipeableDrawer |
Side navigation panel |
Tabs / Tab |
Tabbed navigation |
BottomNavigation / BottomNavigationAction |
Mobile bottom navigation bar |
Breadcrumbs |
Breadcrumb trail |
Link |
Styled anchor element |
Menu / MenuItem / MenuList |
Dropdown menu |
Pagination / PaginationItem |
Page navigation |
SpeedDial / SpeedDialAction / SpeedDialIcon |
Floating action menu |
Stepper / Step / StepLabel / StepButton / StepContent / StepConnector / StepIcon |
Multi-step progress indicator |
import { AppBar, Toolbar, Typography, IconButton } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
<AppBar position="static">
<Toolbar>
<IconButton edge="start" color="inherit" aria-label="menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
My App
</Typography>
</Toolbar>
</AppBar>
Feedback Components
| Component | Purpose |
|---|---|
Alert / AlertTitle |
Status message (success, info, warning, error) |
Dialog / DialogTitle / DialogContent / DialogContentText / DialogActions |
Modal dialog |
Snackbar / SnackbarContent |
Brief notification toast |
Backdrop |
Overlay behind modals/drawers |
CircularProgress / LinearProgress |
Loading indicators |
Skeleton |
Content placeholder during loading |
MobileStepper |
Compact stepper for mobile workflows |
import Alert from '@mui/material/Alert';
<Alert severity="warning" variant="filled">
This action cannot be undone.
</Alert>
Surface Components
| Component | Purpose |
|---|---|
Card / CardContent / CardActions / CardHeader / CardMedia / CardActionArea |
Content card |
Paper |
Elevated surface container |
Accordion / AccordionSummary / AccordionDetails / AccordionActions |
Expandable panel |
Transition Components
| Component | Effect |
|---|---|
Collapse |
Vertical expand/collapse |
Fade |
Opacity transition |
Grow |
Scale + fade transition |
Slide |
Directional slide transition |
Zoom |
Scale transition |
Utility Components
| Component | Purpose |
|---|---|
CssBaseline |
Global CSS reset |
ScopedCssBaseline |
Scoped CSS reset for a subtree |
GlobalStyles |
Inject global CSS |
Modal |
Base modal (used internally by Dialog, Drawer, etc.) |
Popover |
Positioned popup anchored to an element |
Popper |
Low-level popup positioning |
Portal |
Render children into a DOM node outside the parent hierarchy |
ClickAwayListener |
Detect clicks outside an element |
NoSsr |
Suppress server-side rendering for children |
InitColorSchemeScript |
Inject script to prevent dark-mode flash on page load |
Hooks
| Hook | Purpose |
|---|---|
useMediaQuery(query) |
SSR-safe CSS media query evaluation |
useScrollTrigger(options) |
Detect scroll position (for hide-on-scroll AppBar, back-to-top) |
useAutocomplete(options) |
Headless Autocomplete logic (for custom UIs) |
usePagination(options) |
Headless pagination logic |
useFormControl() |
Access parent FormControl state (error, disabled, focused, required) |
useRadioGroup() |
Access parent RadioGroup value and onChange |
import useMediaQuery from '@mui/material/useMediaQuery';
function MyComponent() {
const isMobile = useMediaQuery('(max-width:600px)');
// or with theme: useMediaQuery(theme => theme.breakpoints.down('sm'))
return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>;
}
The Slots and SlotProps Pattern
Every MUI component exposes internal elements as named slots that you can replace or customize:
<Autocomplete
slots={{
paper: CustomPaperComponent,
listbox: VirtualizedListbox,
}}
slotProps={{
paper: { elevation: 8 },
listbox: { style: { maxHeight: 300 } },
// Callback form — receives ownerState
input: (ownerState) => ({
className: ownerState.focused ? 'focused-input' : '',
}),
}}
/>
Tip
slotPropsaccepts either a plain object or a callback function(ownerState) => props. Use the callback when your slot props depend on the component's internal state (focused, disabled, error, etc.). Event handlers fromslotPropsare auto-composed with internal handlers — they do not replace them.
The component Prop (Polymorphism)
Most MUI components accept a component prop to change the rendered HTML element:
import Button from '@mui/material/Button';
import { Link } from 'react-router-dom';
// Renders as <a> via React Router's Link
<Button component={Link} to="/dashboard">
Go to Dashboard
</Button>
// Renders as <li> instead of <div>
<ListItem component="li">Item</ListItem>
Icons
// SVG icon components (recommended)
import DeleteIcon from '@mui/icons-material/Delete';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import DeleteSharpIcon from '@mui/icons-material/DeleteSharp';
import DeleteTwoToneIcon from '@mui/icons-material/DeleteTwoTone';
<DeleteIcon color="primary" fontSize="large" />
Each icon is available in 5 variants: Filled (default), Outlined, Rounded, Sharp, and TwoTone. Over 2,100 icons are available in @mui/icons-material.
Localization
MUI components that render text (e.g., TablePagination, Alert) support localization:
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { frFR } from '@mui/material/locale';
const theme = createTheme({}, frFR); // French locale
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
Available locales include: amET, arEG, arSA, bnBD, csCZ, daDK, deDE, elGR, enUS, esES, etEE, faIR, fiFI, frFR, heIL, hiIN, hrHR, huHU, hyAM, idID, isIS, itIT, jaJP, khKH, koKR, kuCKB, mkMK, msMY, nbNO, nlNL, plPL, ptBR, ptPT, roRO, ruRU, skSK, srRS, svSE, thTH, trTR, ukUA, urPK, viVN, zhCN, zhHK, zhTW, and more.
5. Common Tasks
Build a Login Form
import { Box, TextField, Button, Typography, Paper, Alert } from '@mui/material';
function LoginForm() {
const [error, setError] = React.useState('');
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
const data = new FormData(event.currentTarget as HTMLFormElement);
const email = data.get('email') as string;
const password = data.get('password') as string;
if (!email || !password) {
setError('All fields are required');
return;
}
// Submit login
};
return (
<Paper sx={{ p: 4, maxWidth: 400, mx: 'auto' }}>
<Typography variant="h5" gutterBottom>Sign In</Typography>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<TextField name="email" label="Email" type="email" required fullWidth />
<TextField name="password" label="Password" type="password" required fullWidth />
<Button type="submit" variant="contained" size="large" fullWidth>
Sign In
</Button>
</Box>
</Paper>
);
}
Create a Responsive Navigation Drawer
import { AppBar, Toolbar, Typography, IconButton, Drawer, List, ListItem,
ListItemIcon, ListItemText, useMediaQuery, useTheme } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import HomeIcon from '@mui/icons-material/Home';
import SettingsIcon from '@mui/icons-material/Settings';
function ResponsiveNav() {
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const isDesktop = useMediaQuery(theme.breakpoints.up('md'));
const drawerContent = (
<List sx={{ width: 240 }}>
<ListItem component="a" href="/">
<ListItemIcon><HomeIcon /></ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
<ListItem component="a" href="/settings">
<ListItemIcon><SettingsIcon /></ListItemIcon>
<ListItemText primary="Settings" />
</ListItem>
</List>
);
return (
<>
<AppBar position="fixed">
<Toolbar>
{!isDesktop && (
<IconButton color="inherit" onClick={() => setOpen(true)}>
<MenuIcon />
</IconButton>
)}
<Typography variant="h6">My App</Typography>
</Toolbar>
</AppBar>
<Drawer
variant={isDesktop ? 'permanent' : 'temporary'}
open={isDesktop || open}
onClose={() => setOpen(false)}
>
{drawerContent}
</Drawer>
</>
);
}
Build a Data Table with Sorting and Pagination
import { Table, TableBody, TableCell, TableContainer, TableHead,
TableRow, TablePagination, TableSortLabel, Paper } from '@mui/material';
function DataTable({ rows }) {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const [orderBy, setOrderBy] = React.useState('name');
const [order, setOrder] = React.useState<'asc' | 'desc'>('asc');
const handleSort = (property: string) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
const sortedRows = [...rows].sort((a, b) => {
const cmp = a[orderBy] < b[orderBy] ? -1 : 1;
return order === 'asc' ? cmp : -cmp;
});
return (
<Paper>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>
<TableSortLabel
active={orderBy === 'name'}
direction={order}
onClick={() => handleSort('name')}
>
Name
</TableSortLabel>
</TableCell>
<TableCell align="right">Value</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sortedRows
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.name}</TableCell>
<TableCell align="right">{row.value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div"
count={rows.length}
page={page}
onPageChange={(_, p) => setPage(p)}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={(e) => {
setRowsPerPage(parseInt(e.target.value));
setPage(0);
}}
/>
</Paper>
);
}
Create a Confirmation Dialog
import { Dialog, DialogTitle, DialogContent, DialogContentText,
DialogActions, Button } from '@mui/material';
function ConfirmDialog({ open, onClose, onConfirm, title, message }) {
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{message}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onConfirm} variant="contained" color="error">
Delete
</Button>
</DialogActions>
</Dialog>
);
}
Display a Notification Snackbar
import { Snackbar, Alert, Button } from '@mui/material';
function NotificationExample() {
const [open, setOpen] = React.useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Show Notification</Button>
<Snackbar
open={open}
autoHideDuration={6000}
onClose={() => setOpen(false)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={() => setOpen(false)} severity="success" variant="filled">
Changes saved successfully!
</Alert>
</Snackbar>
</>
);
}
Build a Multi-Step Form (Stepper)
import { Stepper, Step, StepLabel, Button, Typography, Box } from '@mui/material';
const steps = ['Account Details', 'Personal Info', 'Review'];
function MultiStepForm() {
const [activeStep, setActiveStep] = React.useState(0);
return (
<Box sx={{ width: '100%' }}>
<Stepper activeStep={activeStep} sx={{ mb: 4 }}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<Typography sx={{ mb: 2 }}>
Step {activeStep + 1}: {steps[activeStep]}
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
disabled={activeStep === 0}
onClick={() => setActiveStep((s) => s - 1)}
>
Back
</Button>
<Button
variant="contained"
onClick={() => setActiveStep((s) => s + 1)}
>
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
</Button>
</Box>
</Box>
);
}
Implement Async Search with Autocomplete
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import CircularProgress from '@mui/material/CircularProgress';
function AsyncSearch() {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const [loading, setLoading] = React.useState(false);
const handleInputChange = async (_, value) => {
if (value.length < 2) return;
setLoading(true);
const results = await fetchResults(value);
setOptions(results);
setLoading(false);
};
return (
<Autocomplete
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
onInputChange={handleInputChange}
options={options}
loading={loading}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Search"
slotProps={{
input: {
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</>
),
},
}}
/>
)}
/>
);
}
Warning
Autocomplete can experience performance degradation with very large datasets (~1 million items, GitHub issue #33487). For large datasets, implement virtualization via the
slots.listboxprop or use server-side filtering to limit the option set.
Apply Conditional Styles with Theme
import { styled } from '@mui/material/styles';
import Paper from '@mui/material/Paper';
const StyledCard = styled(Paper)(({ theme }) => ({
padding: theme.spacing(3),
borderRadius: theme.shape.borderRadius,
[theme.breakpoints.down('sm')]: {
padding: theme.spacing(1),
},
...theme.applyStyles('dark', {
backgroundColor: theme.palette.grey[900],
}),
}));
Use CSS Variables Theming with Mode Toggle
import {
Experimental_CssVarsProvider as CssVarsProvider,
useColorScheme,
} from '@mui/material/styles';
import Button from '@mui/material/Button';
function ThemeToggle() {
const { mode, setMode } = useColorScheme();
return (
<Button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
Toggle {mode === 'light' ? 'Dark' : 'Light'} Mode
</Button>
);
}
function App() {
return (
<CssVarsProvider>
<ThemeToggle />
</CssVarsProvider>
);
}
Use Lab Tabs with Context
import { TabContext, TabList, TabPanel } from '@mui/lab';
import { Tab, Box } from '@mui/material';
function TabbedView() {
const [value, setValue] = React.useState('1');
return (
<TabContext value={value}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={(_, v) => setValue(v)}>
<Tab label="Overview" value="1" />
<Tab label="Details" value="2" />
<Tab label="Settings" value="3" />
</TabList>
</Box>
<TabPanel value="1">Overview content</TabPanel>
<TabPanel value="2">Details content</TabPanel>
<TabPanel value="3">Settings content</TabPanel>
</TabContext>
);
}
Create a Loading Button
import LoadingButton from '@mui/lab/LoadingButton';
import SaveIcon from '@mui/icons-material/Save';
<LoadingButton
loading={isSaving}
loadingPosition="start"
startIcon={<SaveIcon />}
variant="contained"
onClick={handleSave}
>
Save
</LoadingButton>
Build a Timeline
import {
Timeline, TimelineItem, TimelineSeparator, TimelineConnector,
TimelineContent, TimelineDot, TimelineOppositeContent,
} from '@mui/lab';
<Timeline position="alternate">
<TimelineItem>
<TimelineOppositeContent color="text.secondary">
9:30 am
</TimelineOppositeContent>
<TimelineSeparator>
<TimelineDot color="primary" />
<TimelineConnector />
</TimelineSeparator>
<TimelineContent>Eat breakfast</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineOppositeContent color="text.secondary">
10:00 am
</TimelineOppositeContent>
<TimelineSeparator>
<TimelineDot color="secondary" />
</TimelineSeparator>
<TimelineContent>Start work</TimelineContent>
</TimelineItem>
</Timeline>
Build a Masonry Layout
import Masonry from '@mui/lab/Masonry';
import Paper from '@mui/material/Paper';
<Masonry columns={{ xs: 1, sm: 2, md: 3 }} spacing={2}>
{items.map((item) => (
<Paper key={item.id} sx={{ p: 2 }}>
{item.content}
</Paper>
))}
</Masonry>
6. Troubleshooting
Diagnostic Decision Tree
Component not rendering?
├── Missing ThemeProvider? → Wrap app with ThemeProvider
├── Import error? → Check import path (named vs default)
├── React version mismatch? → Run `npm ls react`, ensure single version
└── Emotion not installed? → Install @emotion/react @emotion/styled
Styles not applying?
├── Using sx on non-MUI element? → Wrap with <Box>
├── CSS specificity issue? → Check injection order, use StyledEngineProvider
├── SSR hydration mismatch? → Ensure Emotion cache configured for SSR
└── Theme not reaching component? → Verify ThemeProvider placement
Component behaving unexpectedly?
├── Controlled/uncontrolled warning? → Provide both value + onChange, or neither
├── Autocomplete issues? → See Autocomplete-specific entries below
└── Modal/Dialog not opening? → Check `open` prop state management
Error: Autocomplete options DOM present when empty in freeSolo mode
Cause: In freeSolo mode, the options listbox DOM node remains present even when there are no options, causing layout or styling issues (GitHub issue #40843).
Solution:
<Autocomplete
freeSolo
slotProps={{
paper: {
sx: { display: options.length === 0 ? 'none' : 'block' },
},
}}
/>
Prevention: Monitor this issue for a future fix in MUI core.
Error: Autocomplete scroll resets when appending options
Cause: When new options are appended to the list after the user scrolls to the bottom (infinite scroll pattern), the list resets to the top (GitHub issue #40250).
Solution: Use a virtualized listbox via the slots.listbox prop and manage scroll position manually.
Prevention: Consider server-side pagination rather than client-side infinite scroll for large datasets.
Error: Autocomplete highlights wrong item after scroll + Enter
Cause: Visual highlight and actual highlighted index can go out of sync in multiple mode when the popup is reopened (GitHub issue #48177), or when scrolling and pressing Enter quickly (GitHub issue #46718).
Solution: Use disableListWrap or implement custom keyboard handling. For issue #46718 (closed), update to the latest MUI version.
Error: Highlighted index not reset when options change
Cause: When the Autocomplete options array changes but keeps the same length, the highlighted index is not reset, pointing to a stale item (GitHub issue #40184).
Solution: Key your Autocomplete or force a remount when the logical option set changes.
Error: Keyboard scrolling broken in fullscreen Dialog
Cause: Fullscreen dialogs may not respond to keyboard scrolling correctly (GitHub issue #42989, closed/fixed).
Solution: Update to the latest MUI version.
Error: Styles from sx prop not applying in production
Cause: CSS injection order differs between development and production when Emotion's default cache prepends styles.
Solution:
import { StyledEngineProvider } from '@mui/material/styles';
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</StyledEngineProvider>
Prevention: Always use StyledEngineProvider with injectFirst if you mix MUI styles with custom CSS or Tailwind.
Error: NVDA screen reader does not announce Select helper text
Cause: The Select component's helper text is not programmatically associated via aria-describedby for NVDA users (GitHub issue #19886, open).
Solution: Manually add aria-describedby linking the Select to its helper text element:
<FormControl>
<InputLabel>Label</InputLabel>
<Select aria-describedby="helper-text">...</Select>
<FormHelperText id="helper-text">Help text</FormHelperText>
</FormControl>
Error: ChromeVox cannot navigate into Dialog content
Cause: ChromeVox linear navigation cannot enter Dialog content in certain configurations (GitHub issue #42622, open).
Solution: Ensure the Dialog has aria-labelledby pointing to DialogTitle and aria-describedby pointing to DialogContent.
Caution
Accessibility issues with screen readers are actively tracked. Test your application with multiple assistive technologies (NVDA, JAWS, VoiceOver, ChromeVox) to catch interaction gaps early.
Error: ListSubheader does not match dark-mode Paper background
Cause: ListSubheader uses a hardcoded background that does not respond to Paper's elevated dark-mode background (GitHub issue #30468).
Solution:
<ListSubheader sx={{ bgcolor: 'inherit' }}>Section Header</ListSubheader>
Error: TimelineItem extra space from ::before pseudo-element
Cause: TimelineItem renders a ::before pseudo-element for opposite content even when no opposite content exists (GitHub issue #46639, closed/fixed).
Solution: Update to the latest MUI version, or manually override:
<TimelineItem sx={{ '&::before': { display: 'none' } }}>
Warning: A component is changing the default/controlled state
Cause: Switching between controlled (value prop) and uncontrolled (no value prop) modes, or changing defaultValue after mount.
Solution: Always provide both value and onChange for controlled mode, or neither for uncontrolled. Never set value to undefined for a controlled component — use empty string '' or null instead.
Warning
Setting
value={undefined}silently switches a controlled component to uncontrolled mode. This is a common source of bugs.
Error: freeSolo Autocomplete value changes to first matching item
Cause: When editing a freeSolo value that partially matches an existing option, the value snaps to that option (GitHub issue #27137, closed/fixed).
Solution: Update to the latest MUI version.
Error: Autocomplete value changes on mouse hover
Cause: In older versions, hovering over options could unexpectedly change the selected value (GitHub issue #20602, closed/fixed, 27 comments).
Solution: Update to the latest MUI version.
7. FAQ
Q: Can I use MUI with React 17?
Yes. MUI v9 supports React 17, 18, and 19. The peer dependency is react ^17.0.0 || ^18.0.0 || ^19.0.0.
Q: How do I reduce my bundle size?
Use path imports (import Button from '@mui/material/Button' instead of import { Button } from '@mui/material'). Ensure your bundler supports tree-shaking and that sideEffects: false is respected. For icons, import only the icons you use — the @mui/icons-material package contains 2,100+ icons across 5 style variants.
Q: How do I override a component's styles without sx?
Three options:
- Theme style overrides — apply globally via
createTheme({ components: { MuiButton: { styleOverrides: { root: {...} }}}}) styled()API — create a styled variant:const MyButton = styled(Button)({ ... })- CSS class overrides — target generated class names (e.g.,
.MuiButton-root)
Q: How do I change the default variant for all TextFields?
Use theme default props:
createTheme({
components: {
MuiTextField: { defaultProps: { variant: 'filled' } },
},
})
Q: What is the difference between @mui/lab and @mui/material?
@mui/lab is the incubator for components not yet stable enough for the core library. Components graduate from lab to material when their API stabilizes. Currently in lab: LoadingButton, Masonry, Timeline components, TabContext/TabList/TabPanel. Date pickers have moved to the separate @mui/x-date-pickers package.
Q: How do I migrate from MUI v5 to v9?
Use the automated codemods sequentially:
npx @mui/codemod v6.0.0/all <path>
npx @mui/codemod v7.0.0/all <path>
npx @mui/codemod v9.0.0/system-props <path>
Use --dry to preview changes first. Each version has specific codemods for breaking changes (grid props, theme color functions, system props, etc.).
Q: Does MUI support server-side rendering?
Yes. For Next.js, install @mui/material-nextjs which handles Emotion cache integration automatically. For Express or custom SSR, use @emotion/server to extract critical CSS. See the Installation section for framework-specific setup.
Q: How do I create a custom palette color?
Extend the palette in your theme:
const theme = createTheme({
palette: {
customColor: {
main: '#ff5722',
light: '#ff8a50',
dark: '#c41c00',
contrastText: '#fff',
},
},
});
Then use it: <Button color="customColor">. For TypeScript, augment the palette module to add type safety for the custom color name.
Q: Will MUI adopt Material Design 3 (Material You)?
This is the most-requested feature (GitHub issue #29345, open). Follow that issue for updates on Material Design 3 adoption plans.
Q: How do I use MUI with Tailwind CSS?
MUI works alongside Tailwind CSS. Install both and configure Tailwind normally. Use StyledEngineProvider with injectFirst to ensure Tailwind utilities can override MUI styles when needed. MUI provides an official Vite + Tailwind example project.
Q: What is Pigment CSS?
Pigment CSS is MUI's experimental zero-runtime CSS extraction engine. Install @mui/material-pigment-css and the Pigment CSS plugin for your bundler to extract styles at build time, eliminating the Emotion runtime. This is experimental — the packages/mui-material-pigment-css package tracks current status.
Q: How do I use the v9.0.0/system-props codemod with auto-import plugins?
The v9 system-props codemod may be incompatible with unplugin-auto-import (GitHub issue #48269, open). If your project uses auto-importing, you may need to manually adjust transformed files.
Q: How do I add pagination support to Autocomplete for async requests?
Native Autocomplete pagination is a requested feature (GitHub issue #18450, open). Currently, implement pagination manually by appending results to the options array and managing loading state. See the async search recipe in Common Tasks.