# Material UI (MUI) User Guide

## 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:**

```bash
npm install @mui/material @emotion/react @emotion/styled
```

```jsx
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"
    ```bash
    npm install @mui/material @emotion/react @emotion/styled
    ```

=== "yarn"
    ```bash
    yarn add @mui/material @emotion/react @emotion/styled
    ```

=== "pnpm"
    ```bash
    pnpm add @mui/material @emotion/react @emotion/styled
    ```

=== "CDN"
    ```html
    <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:

```bash
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:

```javascript
// 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:

```bash
npm install @fontsource/roboto
```

```javascript
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:

```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:

```html
<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

```bash
npx @mui/envinfo
```

```text
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)

```bash
npm install @mui/material @emotion/react @emotion/styled @mui/material-nextjs
```

```tsx
// 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)

```tsx
// 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

```bash
npm install @mui/material @emotion/react @emotion/styled
```

```tsx
// 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:

```bash
npm install @mui/material @emotion/react @emotion/styled @emotion/server
```

```javascript
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:
   ```bash
   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:
   ```bash
   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:
```tsx
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:
```javascript
// 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`:

```tsx
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

```tsx
const darkTheme = createTheme({
  palette: {
    mode: 'dark',
  },
});
```

For automatic system-preference detection with CSS variables:

```tsx
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:

```tsx
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.

```tsx
theme.spacing(2); // '16px'
theme.spacing(1, 2); // '8px 16px'
```

### Component Default Props

Override default props globally for any component:

```tsx
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:

```tsx
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:

```tsx
<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:**

```tsx
<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 |

```tsx
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 |

```tsx
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 |

```tsx
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 |

```tsx
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 |

```tsx
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 |

```tsx
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:

```tsx
<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:

```tsx
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

```tsx
// 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:

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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)

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```text
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:**
```tsx
<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:**
```tsx
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:
```tsx
<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:**
```tsx
<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:
```tsx
<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:
```tsx
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:
```bash
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:
```tsx
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.

---

_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. Unofficial; not affiliated with or reviewed by the project._
