Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Material UI (MUI) User Guide

Generated May 2026 from mui/material-ui at 3dcaf4b

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 sx prop for inline, theme-aware styling on any MUI component
  • Slots and slotProps API for deep component customization
  • Polymorphic component prop 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/react and @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-based Icon component 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:

  1. Install the required packages:
    npm install @emotion/react @emotion/styled
    
  2. 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:

  1. Check for duplicate React:
    npm ls react
    
  2. Ensure only one React version is resolved
  3. 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

CssBaseline resets browser default styles (similar to normalize.css). Place it inside ThemeProvider to apply theme-aware resets. Use ScopedCssBaseline to 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 sx prop only works on MUI components. It does not work on plain HTML elements like <div>. Wrap plain elements with <Box> if you need sx support.

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

TextField is a convenience wrapper combining FormControl, InputLabel, Input (or OutlinedInput/FilledInput), and FormHelperText. 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>
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

slotProps accepts 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 from slotProps are 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.listbox prop 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:

  1. Theme style overrides — apply globally via createTheme({ components: { MuiButton: { styleOverrides: { root: {...} }}}})
  2. styled() API — create a styled variant: const MyButton = styled(Button)({ ... })
  3. 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.