MUI (Material UI & Joy UI)
MUI v7 — LLM Reference Guide
Current Version: Material UI 7.x (2025–2026) MUI v7 builds on v6 with improved ESM support, standardized slot pattern (
slots/slotProps), CSS layers viaenableCssLayer, Grid2→Grid rename, and removed v5 deprecated APIs. If your prior knowledge is based on v5 or v6, read the "Migration Pitfalls" section before generating any code.
1. Installation
Material UI (Default — Emotion)
# npm
npm install @mui/material @emotion/react @emotion/styled
# pnpm
pnpm add @mui/material @emotion/react @emotion/styled
# yarn
yarn add @mui/material @emotion/react @emotion/styled
With styled-components (Alternative)
npm install @mui/material @mui/styled-engine-sc styled-components
Note: Emotion is strongly recommended for SSR projects.
styled-componentshas compatibility issues with server-rendered MUI apps.
Joy UI
# npm
npm install @mui/joy @emotion/react @emotion/styled
# pnpm
pnpm add @mui/joy @emotion/react @emotion/styled
Warning: Joy UI is in beta and development is currently on hold. For new projects, Material UI is recommended.
Peer Dependencies
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
Important (React 18 and below): MUI v7 uses
react-is@19. If you're on React 18 or below, you must install a matchingreact-isversion and setoverrides(npm) orresolutions(yarn) inpackage.jsonto prevent runtime errors:npm install react-is@18.3.1{ "overrides": { "react-is": "^18.3.1" } }
Roboto Font (Material UI default)
npm install @fontsource/roboto
// Entry point (e.g., index.js or App.js)
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
Or via Google Fonts CDN:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
/>
Material Icons
npm install @mui/icons-material
Or via CDN:
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
CssBaseline (Recommended)
Always include CssBaseline to normalize browser styles:
import CssBaseline from "@mui/material/CssBaseline";
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* Your app */}
</ThemeProvider>
);
}
2. Theming (createTheme & ThemeProvider)
Material UI uses a JavaScript theme object for all design tokens. Create a theme with createTheme() and inject it via ThemeProvider.
import { createTheme, ThemeProvider } from "@mui/material/styles";
const theme = createTheme({
palette: {
primary: { main: "#1976d2" },
secondary: { main: "#9c27b0" },
},
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", sans-serif',
},
shape: {
borderRadius: 8,
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* App content */}
</ThemeProvider>
);
}
Theme Configuration Variables
| Key | Purpose |
|---|---|
palette |
Colors (primary, secondary, error, etc.) |
typography |
Font family, sizes, weights, variants |
spacing |
Base spacing unit (default: 8px) |
breakpoints |
Responsive breakpoint values |
shape |
Border radius |
zIndex |
Z-index values for layered components |
transitions |
Duration, easing for animations |
components |
Component-level default props & styles |
colorSchemes |
Light/dark mode color definitions (v6+) |
Accessing the Theme
import { useTheme } from "@mui/material/styles";
function MyComponent() {
const theme = useTheme();
return <span>{theme.spacing(2)}</span>; // "16px"
}
Nesting Themes
<ThemeProvider theme={outerTheme}>
<Button>Outer theme</Button>
<ThemeProvider theme={innerTheme}>
<Button>Inner theme overrides outer</Button>
</ThemeProvider>
</ThemeProvider>
You can also extend the outer theme with a function:
<ThemeProvider theme={(outerTheme) => createTheme({ ...outerTheme, /* overrides */ })}>
Custom Theme Variables
const theme = createTheme({
status: {
danger: '#e53e3e',
},
});
// TypeScript: Augment the theme
declare module '@mui/material/styles' {
interface Theme {
status: { danger: string };
}
interface ThemeOptions {
status?: { danger?: string };
}
}
3. Palette & Colors
Color Tokens
Each palette color has four tokens:
| Token | Description |
|---|---|
main |
The main shade (required) |
light |
Lighter shade (auto-calculated) |
dark |
Darker shade (auto-calculated) |
contrastText |
Text color contrasting main (auto) |
Default Palette Colors
| Key | Purpose | Default Main |
|---|---|---|
primary |
Primary interface elements | #1976d2 |
secondary |
Secondary interface elements | #9c27b0 |
error |
Error states | #d32f2f |
warning |
Warning states | #ed6c02 |
info |
Informational elements | #0288d1 |
success |
Success states | #2e7d32 |
Custom Palette
const theme = createTheme({
palette: {
primary: {
main: "#FF5733",
// light, dark, contrastText auto-calculated
},
secondary: {
main: "#E0C2FF",
light: "#F5EBFF",
contrastText: "#47008F",
},
},
});
Using MUI Color Objects
import { createTheme } from "@mui/material/styles";
import { lime, purple } from "@mui/material/colors";
const theme = createTheme({
palette: {
primary: lime,
secondary: purple,
},
});
Adding Custom Colors
const theme = createTheme({
palette: {
ochre: {
main: '#E3D026',
light: '#E9DB5D',
dark: '#A29415',
contrastText: '#242105',
},
},
});
// TypeScript: Augment palette
declare module '@mui/material/styles' {
interface Palette {
ochre: Palette['primary'];
}
interface PaletteOptions {
ochre?: PaletteOptions['primary'];
}
}
// Augment component props
declare module '@mui/material/Button' {
interface ButtonPropsColorOverrides {
ochre: true;
}
}
Using augmentColor
const theme = createTheme({
palette: {
ochre: theme.palette.augmentColor({
color: { main: "#E3D026" },
name: "ochre",
}),
},
});
4. Typography
Default Variants
MUI provides 13 typography variants:
| Variant | Default Element | Typical Use |
|---|---|---|
h1 |
<h1> |
Page title |
h2 |
<h2> |
Section title |
h3 |
<h3> |
Subsection |
h4 |
<h4> |
Card title |
h5 |
<h5> |
Sub-heading |
h6 |
<h6> |
Small heading |
subtitle1 |
<h6> |
Subtitle |
subtitle2 |
<h6> |
Small subtitle |
body1 |
<p> |
Body text |
body2 |
<p> |
Secondary text |
button |
<span> |
Button label |
caption |
<span> |
Small caption |
overline |
<span> |
Overline text |
Usage
import Typography from '@mui/material/Typography';
<Typography variant="h4" component="h1" gutterBottom>
Page Title
</Typography>
<Typography variant="body1">
Body content text here.
</Typography>
Customizing Typography
const theme = createTheme({
typography: {
fontFamily: '"Inter", "Roboto", sans-serif',
h1: {
fontSize: "2.5rem",
fontWeight: 700,
},
body1: {
fontSize: "1rem",
lineHeight: 1.6,
},
button: {
textTransform: "none", // Disable uppercase buttons
},
},
});
Adding Custom Variants
const theme = createTheme({
typography: {
poster: {
fontSize: '4rem',
fontWeight: 800,
color: 'red',
},
},
components: {
MuiTypography: {
defaultProps: {
variantMapping: {
poster: 'h1', // Maps <Typography variant="poster"> to <h1>
},
},
},
},
});
// TypeScript augmentation
declare module '@mui/material/styles' {
interface TypographyVariants {
poster: React.CSSProperties;
}
interface TypographyVariantsOptions {
poster?: React.CSSProperties;
}
}
declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
poster: true;
}
}
Responsive Font Sizes
import { createTheme, responsiveFontSizes } from "@mui/material/styles";
let theme = createTheme();
theme = responsiveFontSizes(theme);
// h1–h6 now scale down automatically on smaller screens
5. Responsive Design
Default Breakpoints
| Key | Min-width | Description |
|---|---|---|
xs |
0px | Extra-small |
sm |
600px | Small |
md |
900px | Medium |
lg |
1200px | Large |
xl |
1536px | Extra-large |
Breakpoint Helpers (in styled / sx)
// In styled or sx:
{
[theme.breakpoints.up('md')]: {
fontSize: '1.2rem',
},
[theme.breakpoints.down('sm')]: {
padding: theme.spacing(1),
},
[theme.breakpoints.between('sm', 'lg')]: {
maxWidth: 600,
},
[theme.breakpoints.only('md')]: {
backgroundColor: '#f5f5f5',
},
}
Responsive sx Prop (Object Syntax)
<Box
sx={{
width: {
xs: "100%", // 0px+
sm: "80%", // 600px+
md: "60%", // 900px+
lg: "50%", // 1200px+
},
}}
/>
useMediaQuery Hook
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
function MyComponent() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const isDesktop = useMediaQuery(theme.breakpoints.up("lg"));
return isMobile ? <MobileView /> : <DesktopView />;
}
Custom Breakpoints
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 900,
lg: 1200,
xl: 1536,
xxl: 1920, // Custom
},
},
});
6. The sx Prop
The sx prop is the primary way to add one-off custom styles to MUI components. It is theme-aware, supports responsive values, and supports pseudo-selectors.
Basic Usage
<Box
sx={{
bgcolor: "primary.main",
color: "primary.contrastText",
p: 2, // padding: theme.spacing(2) = 16px
m: 1, // margin: 8px
borderRadius: 1, // theme.shape.borderRadius * 1
boxShadow: 3, // theme.shadows[3]
fontSize: "h6.fontSize",
}}
>
Styled Box
</Box>
Shorthand Properties
sx Key |
CSS Property | Theme Mapping |
|---|---|---|
m, mx, my, mt, mr, mb, ml |
margin-* |
theme.spacing() |
p, px, py, pt, pr, pb, pl |
padding-* |
theme.spacing() |
bgcolor |
background-color |
theme.palette |
color |
color |
theme.palette |
boxShadow |
box-shadow |
theme.shadows |
borderRadius |
border-radius |
theme.shape |
typography |
font styles | theme.typography |
zIndex |
z-index |
theme.zIndex |
gap |
gap |
theme.spacing() |
display |
display |
— |
width, height, minWidth, maxWidth |
sizing | Fraction → % |
Responsive sx Values
<Box
sx={{
flexDirection: { xs: "column", md: "row" },
gap: { xs: 1, sm: 2, md: 3 },
p: { xs: 1, md: 3 },
}}
/>
Callback Values
<Box
sx={(theme) => ({
...theme.typography.body1,
color: theme.palette.primary.main,
border: `1px solid ${theme.palette.divider}`,
})}
/>
v6+ Change: Callback as individual value is deprecated. Use callback at the top level.
Array Values (Conditional Styles)
<Box
sx={[
{ color: "red", "&:hover": { backgroundColor: "white" } },
isActive && { "&:hover": { backgroundColor: "grey" } },
isHighlighted && { "&:hover": { backgroundColor: "yellow" } },
]}
/>
Nested Selectors & Pseudo-classes
<Box
sx={{
"&:hover": { opacity: 0.8 },
"&:focus-within": { outline: "2px solid blue" },
"& .MuiButton-root": { textTransform: "none" },
"& > *:not(:last-child)": { mb: 2 },
}}
/>
7. Dark Mode
Simple Dark Theme
import { createTheme, ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
const darkTheme = createTheme({
palette: {
mode: "dark",
},
});
function App() {
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<main>Dark mode app</main>
</ThemeProvider>
);
}
Built-in Light/Dark Support (CSS Variables — v6+ Recommended)
const theme = createTheme({
cssVariables: true,
colorSchemes: {
dark: true, // Enables both light and dark
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* Automatically supports light/dark via system preference */}
</ThemeProvider>
);
}
Toggling Mode with useColorScheme
import { useColorScheme } from "@mui/material/styles";
function ModeToggle() {
const { mode, setMode } = useColorScheme();
if (!mode) return null; // Prevent hydration mismatch
return (
<Button onClick={() => setMode(mode === "dark" ? "light" : "dark")}>
{mode === "dark" ? "☀️ Light" : "🌙 Dark"}
</Button>
);
}
Styling in Dark Mode with theme.applyStyles()
// Preferred approach (v6/v7)
<Box
sx={(theme) => ({
bgcolor: "#fff",
color: "#000",
...theme.applyStyles("dark", {
bgcolor: "#121212",
color: "#fff",
}),
})}
/>
Preventing SSR Dark Mode Flicker
Use CSS theme variables (cssVariables: true) plus InitColorSchemeScript:
import InitColorSchemeScript from "@mui/material/InitColorSchemeScript";
// In your HTML head (e.g., Next.js _document or layout)
<InitColorSchemeScript />;
8. CSS Theme Variables
Enable CSS custom properties from your MUI theme for better DevTools debugging, no dark-mode flicker, and easier third-party integration.
const theme = createTheme({
cssVariables: true,
});
This generates global CSS like:
:root {
--mui-palette-primary-main: #1976d2;
--mui-palette-background-default: #fff;
--mui-shape-borderRadius: 4px;
/* ... */
}
Components automatically use var(--mui-palette-primary-main) instead of raw values.
Advantages
- Prevents dark-mode SSR flickering
- Unlimited color schemes beyond light/dark
- Better DevTools debugging (see token names, not raw values)
- Automatic sync between browser tabs
- Easier third-party integration
9. Grid System
MUI v7 uses a unified Grid component (CSS Flexbox-based, 12-column system). In v7, the old Grid (v1) was renamed to GridLegacy, and Grid2 was promoted to Grid.
Basic Usage
import Grid from "@mui/material/Grid";
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 8 }}>
<Item>xs=12 md=8</Item>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Item>xs=12 md=4</Item>
</Grid>
</Grid>;
Key Props
| Prop | Description |
|---|---|
container |
Makes the Grid a flex container |
size |
Column span (number 1–12, "auto", or "grow") |
spacing |
Gap between items (number, uses theme.spacing) |
direction |
Flex direction (row, column, etc.) |
offset |
Column offset ({ md: 2 } offsets by 2 columns at md+) |
columns |
Total number of columns (default: 12) |
Responsive Grid
<Grid container spacing={{ xs: 1, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<Card>Responsive card</Card>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
<Card>Responsive card</Card>
</Grid>
</Grid>
Auto-layout
<Grid container spacing={2}>
<Grid size="grow">Fills remaining space</Grid>
<Grid size="auto">Fits content</Grid>
<Grid size={6}>Fixed 6 columns</Grid>
</Grid>
Nested Grids
<Grid container spacing={2}>
<Grid size={6}>
<Grid container spacing={1}>
<Grid size={6}>Nested</Grid>
<Grid size={6}>Nested</Grid>
</Grid>
</Grid>
</Grid>
Grid with Offset
<Grid container spacing={2}>
<Grid size={6} offset={{ md: 3 }}>
Centered 6-column item
</Grid>
</Grid>
10. Layout Components
Box
The fundamental layout primitive. Renders a <div> by default, supports all sx props.
import Box from "@mui/material/Box";
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 2,
p: 3,
bgcolor: "background.paper",
borderRadius: 2,
boxShadow: 1,
}}
>
<Typography>Content</Typography>
</Box>;
Stack
One-dimensional layout (vertical or horizontal).
import Stack from "@mui/material/Stack";
<Stack direction="row" spacing={2} alignItems="center">
<Button variant="contained">One</Button>
<Button variant="outlined">Two</Button>
<Button variant="text">Three</Button>
</Stack>;
{
/* Responsive direction */
}
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={{ xs: 1, sm: 2, md: 4 }}
>
<Item>Item 1</Item>
<Item>Item 2</Item>
</Stack>;
Container
Centered, max-width container for page-level layout.
import Container from "@mui/material/Container";
<Container maxWidth="lg">
<Typography>Centered content (max 1200px)</Typography>
</Container>;
{
/* Fixed width */
}
<Container fixed>
<Typography>Fixed-width container</Typography>
</Container>;
11. Component Customization
1. One-off: sx prop
<Button sx={{ borderRadius: 28, textTransform: "none" }}>Custom Button</Button>
2. Reusable: styled() API
import { styled } from "@mui/material/styles";
import Button from "@mui/material/Button";
const GradientButton = styled(Button)(({ theme }) => ({
background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
borderRadius: 8,
color: "white",
padding: "8px 24px",
boxShadow: "0 3px 5px 2px rgba(255, 105, 135, .3)",
"&:hover": {
background: "linear-gradient(45deg, #FE6B8B 60%, #FF8E53 90%)",
},
}));
<GradientButton>Gradient</GradientButton>;
3. Global: Theme components key
Default Props
const theme = createTheme({
components: {
MuiButton: {
defaultProps: {
variant: "contained",
disableElevation: true,
},
},
MuiTextField: {
defaultProps: {
variant: "outlined",
size: "small",
},
},
},
});
Style Overrides
const theme = createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
textTransform: "none",
fontWeight: 600,
},
contained: {
boxShadow: "none",
"&:hover": {
boxShadow: "none",
},
},
},
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 12,
boxShadow: "0 2px 12px rgba(0,0,0,0.08)",
},
},
},
},
});
Custom Variants
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
props: { variant: 'dashed' },
style: {
border: '2px dashed currentColor',
borderRadius: 8,
},
},
{
props: { variant: 'dashed', color: 'secondary' },
style: {
border: '2px dashed #9c27b0',
},
},
],
},
},
});
// TypeScript
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
dashed: true;
}
}
12. Common Components Quick Reference
Inputs
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import TextField from '@mui/material/TextField';
import Checkbox from '@mui/material/Checkbox';
import Radio from '@mui/material/Radio';
import Switch from '@mui/material/Switch';
import Select from '@mui/material/Select';
import Slider from '@mui/material/Slider';
import Autocomplete from '@mui/material/Autocomplete';
{/* Button variants */}
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
{/* Button colors */}
<Button color="primary">Primary</Button>
<Button color="secondary">Secondary</Button>
<Button color="error">Error</Button>
<Button color="warning">Warning</Button>
<Button color="info">Info</Button>
<Button color="success">Success</Button>
{/* Button sizes */}
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
<Button size="large">Large</Button>
{/* Button with icon */}
<Button variant="contained" startIcon={<SaveIcon />}>Save</Button>
<Button variant="outlined" endIcon={<SendIcon />}>Send</Button>
{/* Loading button */}
<Button loading>Loading</Button>
<Button loading loadingPosition="start" startIcon={<SaveIcon />}>Save</Button>
{/* TextField */}
<TextField label="Name" variant="outlined" />
<TextField label="Name" variant="filled" />
<TextField label="Name" variant="standard" />
<TextField label="Email" type="email" required error helperText="Invalid email" />
<TextField label="Bio" multiline rows={4} />
Data Display
import Typography from '@mui/material/Typography';
import Avatar from '@mui/material/Avatar';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import List from '@mui/material/List';
import Table from '@mui/material/Table';
import Tooltip from '@mui/material/Tooltip';
import Badge from '@mui/material/Badge';
{/* Typography */}
<Typography variant="h4" gutterBottom>Heading</Typography>
<Typography variant="body1" color="text.secondary">Body text</Typography>
{/* Chip */}
<Chip label="React" color="primary" />
<Chip label="Deletable" onDelete={handleDelete} />
<Chip label="Clickable" onClick={handleClick} variant="outlined" />
{/* Avatar */}
<Avatar src="/photo.jpg" alt="Name" />
<Avatar sx={{ bgcolor: 'primary.main' }}>AB</Avatar>
{/* Badge */}
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
Surfaces
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Paper from '@mui/material/Paper';
import Accordion from '@mui/material/Accordion';
import AppBar from '@mui/material/AppBar';
{/* Card */}
<Card elevation={3}>
<CardContent>
<Typography variant="h5">Title</Typography>
<Typography variant="body2" color="text.secondary">
Description text
</Typography>
</CardContent>
<CardActions>
<Button size="small">Learn More</Button>
</CardActions>
</Card>
{/* Paper */}
<Paper elevation={2} sx={{ p: 2 }}>Content on paper</Paper>
<Paper variant="outlined" sx={{ p: 2 }}>Outlined paper</Paper>
Navigation
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
import Drawer from "@mui/material/Drawer";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import BottomNavigation from "@mui/material/BottomNavigation";
import Breadcrumbs from "@mui/material/Breadcrumbs";
import Link from "@mui/material/Link";
{
/* AppBar */
}
<AppBar position="static">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
My App
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>;
{
/* Tabs */
}
<Tabs value={value} onChange={handleChange}>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
<Tab label="Tab 3" />
</Tabs>;
Feedback
import Dialog from '@mui/material/Dialog';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import CircularProgress from '@mui/material/CircularProgress';
import LinearProgress from '@mui/material/LinearProgress';
import Skeleton from '@mui/material/Skeleton';
{/* Alert */}
<Alert severity="error">This is an error alert</Alert>
<Alert severity="warning">This is a warning alert</Alert>
<Alert severity="info">This is an info alert</Alert>
<Alert severity="success">This is a success alert</Alert>
<Alert variant="filled" severity="success">Filled success</Alert>
<Alert variant="outlined" severity="info">Outlined info</Alert>
{/* Progress */}
<CircularProgress />
<CircularProgress variant="determinate" value={75} />
<LinearProgress />
<LinearProgress variant="determinate" value={50} />
{/* Skeleton */}
<Skeleton variant="text" width={210} />
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={210} height={118} />
<Skeleton variant="rounded" width={210} height={60} />
13. The styled() API
Basic Usage
import { styled } from "@mui/material/styles";
const StyledDiv = styled("div")(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
color: theme.palette.text.primary,
}));
Styling MUI Components
import { styled } from "@mui/material/styles";
import Card from "@mui/material/Card";
const GlassCard = styled(Card)(({ theme }) => ({
background: "rgba(255, 255, 255, 0.1)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.2)",
borderRadius: 16,
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.1)",
}));
Dynamic Styles with Props
const FlexBox = styled(Box, {
shouldForwardProp: (prop) => prop !== "gap" && prop !== "direction",
})(({ theme, gap = 2, direction = "row" }) => ({
display: "flex",
flexDirection: direction,
gap: theme.spacing(gap),
alignItems: "center",
}));
<FlexBox gap={3} direction="column">
<Typography>Item</Typography>
</FlexBox>;
Dark Mode in styled
const StyledCard = styled(Card)(({ theme }) => ({
backgroundColor: "#fff",
...theme.applyStyles("dark", {
backgroundColor: "#1a1a2e",
}),
}));
14. Spacing System
MUI uses a factor-based spacing system. The default factor is 8px.
theme.spacing(1) // '8px'
theme.spacing(2) // '16px'
theme.spacing(0.5) // '4px'
theme.spacing(3) // '24px'
// In sx prop
<Box sx={{ p: 2, m: 1, gap: 3 }} />
// p: 16px, m: 8px, gap: 24px
// Custom spacing
const theme = createTheme({
spacing: 4, // factor = 4px
});
// theme.spacing(2) = '8px'
// Custom function
const theme = createTheme({
spacing: (factor) => `${0.25 * factor}rem`,
});
// theme.spacing(2) = '0.5rem'
15. Transitions & Animations
Theme Transitions
<Box
sx={{
transition: (theme) =>
theme.transitions.create(["background-color", "transform"], {
duration: theme.transitions.duration.standard, // 300ms
easing: theme.transitions.easing.easeInOut,
}),
"&:hover": {
backgroundColor: "primary.dark",
transform: "scale(1.02)",
},
}}
/>
Transition Components
import Fade from '@mui/material/Fade';
import Grow from '@mui/material/Grow';
import Slide from '@mui/material/Slide';
import Collapse from '@mui/material/Collapse';
import Zoom from '@mui/material/Zoom';
<Fade in={show} timeout={500}>
<Box>Fades in</Box>
</Fade>
<Grow in={show}>
<Box>Grows in</Box>
</Grow>
<Slide direction="up" in={show}>
<Box>Slides up</Box>
</Slide>
<Collapse in={show}>
<Box>Collapses/Expands</Box>
</Collapse>
<Zoom in={show}>
<Box>Zooms in</Box>
</Zoom>
16. Joy UI Quick Reference
Status: Joy UI is in beta and development is on hold. Use Material UI for new projects.
Joy UI is MUI's alternative component library with its own design system ("Joy Design") — a cleaner, more modern aesthetic that doesn't follow Material Design.
Installation
npm install @mui/joy @emotion/react @emotion/styled
Key Differences from Material UI
| Feature | Material UI | Joy UI |
|---|---|---|
| Design System | Material Design 2 | Joy Design (custom) |
| Theme Provider | ThemeProvider |
CssVarsProvider |
| Create Theme | createTheme() |
extendTheme() |
| CSS Variables | Opt-in | Built-in by default |
| Color System | palette.mode |
CSS variables w/ modes |
| Import Path | @mui/material/* |
@mui/joy/* |
| Customization | sx, styled, theme |
sx, styled, CSS vars |
Basic Usage
import { CssVarsProvider, extendTheme } from "@mui/joy/styles";
import CssBaseline from "@mui/joy/CssBaseline";
import Button from "@mui/joy/Button";
import Sheet from "@mui/joy/Sheet";
import Typography from "@mui/joy/Typography";
const theme = extendTheme({
colorSchemes: {
light: {
palette: {
primary: {
50: "#e3f2fd",
500: "#2196f3",
900: "#0d47a1",
},
},
},
},
});
function App() {
return (
<CssVarsProvider theme={theme}>
<CssBaseline />
<Sheet variant="outlined" sx={{ p: 3, borderRadius: "md" }}>
<Typography level="h4">Hello Joy UI</Typography>
<Button variant="solid" color="primary">
Click me
</Button>
</Sheet>
</CssVarsProvider>
);
}
Joy UI Component Variants
Joy UI components support four variants:
| Variant | Description |
|---|---|
solid |
Filled background |
soft |
Light tinted background |
outlined |
Border only |
plain |
No background or border |
<Button variant="solid">Solid</Button>
<Button variant="soft">Soft</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="plain">Plain</Button>
Joy UI CSS Variables
Joy UI exposes CSS variables on every component for easy customization:
<List
sx={{
"--ListItem-radius": "8px",
"--ListItem-minHeight": "40px",
"--List-gap": "4px",
"--List-padding": "8px",
}}
>
<ListItem>Item 1</ListItem>
<ListItem>Item 2</ListItem>
</List>
17. Import Best Practices
Named Imports (Tree-shakeable)
// ✅ Recommended — tree-shakeable, one import per component
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { createTheme, ThemeProvider } from "@mui/material/styles";
v7 Breaking Change: Deep imports with more than one level are no longer supported.
// ❌ No longer works in v7: import createTheme from "@mui/material/styles/createTheme"; // ✅ Use named import instead: import { createTheme } from "@mui/material/styles";
Barrel Imports (Convenient but larger bundles)
// ⚠️ Works but may increase bundle size without tree-shaking
import { Button, TextField, Box } from "@mui/material";
Icons
// ✅ Individual imports (recommended)
import DeleteIcon from "@mui/icons-material/Delete";
import SearchIcon from "@mui/icons-material/Search";
// ⚠️ Barrel import (larger bundle)
import { Delete, Search } from "@mui/icons-material";
18. CSS Layers (v7)
MUI v7 supports opt-in CSS layers via the enableCssLayer prop. This wraps MUI styles in a @layer mui rule, improving integration with utility-first frameworks like Tailwind CSS v4 and giving you full control over style precedence.
Client-side Apps (Vite, CRA)
import { StyledEngineProvider } from "@mui/material/styles";
function App() {
return (
<StyledEngineProvider enableCssLayer>
<ThemeProvider theme={theme}>
<CssBaseline />
{/* Your app */}
</ThemeProvider>
</StyledEngineProvider>
);
}
Next.js App Router
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
export default function RootLayout({ children }) {
return (
<html>
<body>
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
{children}
</AppRouterCacheProvider>
</body>
</html>
);
}
Benefits
- Avoids specificity conflicts with Tailwind CSS v4 and other utility frameworks
- Clear style ordering in browser DevTools
- Your custom CSS can easily override MUI styles without
!important
19. Standardized Slot Pattern (v7)
MUI v7 standardized the slots / slotProps API across all components. The older components / componentsProps props are removed.
Usage
// ✅ v7: Use slots and slotProps
<TextField
slots={{ input: CustomInput }}
slotProps={{
input: { className: 'custom-input' },
htmlInput: { 'aria-label': 'Name' },
}}
/>
<Autocomplete
slots={{ paper: CustomPaper, popper: CustomPopper }}
slotProps={{
paper: { elevation: 8 },
listbox: { sx: { maxHeight: 300 } },
}}
/>
// ❌ v5/v6 deprecated (removed in v7):
// <TextField InputProps={{ ... }} inputProps={{ ... }} />
// <Autocomplete PaperComponent={...} PopperComponent={...} />
Key Component Slots
| Component | Common Slots |
|---|---|
TextField |
root, input, inputLabel, formHelperText, htmlInput, select |
Autocomplete |
root, input, clearIndicator, popupIndicator, listbox, paper, popper |
Avatar |
root, img, fallback |
Badge |
root, badge |
Dialog |
root, backdrop, paper, container |
Prop to slotProps Mapping
| Old Prop | v7 slotProps Equivalent |
|---|---|
InputProps |
slotProps={{ input: { ... } }} |
inputProps |
slotProps={{ htmlInput: { ... } }} |
InputLabelProps |
slotProps={{ inputLabel: { ... } }} |
FormHelperTextProps |
slotProps={{ formHelperText: { ... } }} |
SelectProps |
slotProps={{ select: { ... } }} |
PaperComponent |
slots={{ paper: ... }} |
PopperComponent |
slots={{ popper: ... }} |
Key Points
slotslets you replace internal component elements with custom componentsslotPropspasses props to each slot's rendered element- Consistent API across all MUI components
20. Migration Pitfalls (v5/v6 → v7)
CRITICAL: LLMs trained on older data will suggest deprecated v5/v6 patterns. Always use the v7 patterns below.
| ❌ v5/v6 (DO NOT USE) | ✅ v7 (USE THIS) |
|---|---|
<Grid item xs={6}> |
<Grid size={{ xs: 6 }}> (unified Grid) |
<Grid item xs={12} sm={6}> |
<Grid size={{ xs: 12, sm: 6 }}> |
<Grid container item> |
<Grid container> (all Grids are items by default) |
import Grid from '@mui/material/Grid2' |
import Grid from '@mui/material/Grid' (Grid2 is now Grid) |
palette.mode: 'dark' only |
colorSchemes: { dark: true } + cssVariables: true |
sx={{ color: (theme) => theme.palette.primary.main }} |
sx={(theme) => ({ color: theme.palette.primary.main })} |
theme.palette.mode === 'dark' ? ... : ... |
theme.applyStyles('dark', { ... }) or theme.vars.* |
Deep imports: @mui/material/styles/createTheme |
import { createTheme } from '@mui/material/styles' |
InputProps, inputProps, PaperComponent, etc. |
slots / slotProps (standardized in v7) |
<InputLabel size="normal"> |
<InputLabel size="medium"> (standardized in v7) |
@mui/styles (JSS) / makeStyles() / withStyles() |
styled() or sx prop (Emotion) |
<Hidden xsDown> / <Hidden mdUp> |
useMediaQuery() or responsive sx={{ display: { xs: 'none' } }} |
onBackdropClick on Dialog/Modal |
Use onClose callback with reason === 'backdropClick' |
createMuiTheme() |
createTheme() |
experimentalStyled |
styled |
Components from @mui/lab (graduated) |
Import from @mui/material (Alert, Skeleton, Rating, etc.) |
No shouldForwardProp in styled |
Use shouldForwardProp to prevent custom props on DOM |
What Still Works
- All component names (
Button,Card,TextField, etc.) remain the same. - The
sxprop syntax is largely the same. createTheme()andThemeProviderare the same.- The
styled()API from@mui/material/stylesis the same. - Color palette keys (
primary,secondary,error, etc.) are unchanged. - CSS variables,
colorSchemes,theme.applyStyles()all carry over from v6.
v7 Theme Behavior Change
With CSS variables enabled + light/dark color schemes, the theme object no longer re-renders when toggling dark mode. Use theme.vars.* to reference CSS variables directly in styles:
const Custom = styled("div")(({ theme }) => ({
color: theme.vars.palette.text.primary,
background: theme.vars.palette.primary.main,
}));
For runtime calculations, use color-mix() instead of JS:
color: `color-mix(in srgb, ${theme.vars.palette.text.primary}, transparent 50%)`;
If you need the old re-render behavior:
<ThemeProvider forceThemeRerender />
v7 Codemods
Use these commands for automated migration:
# Rename Grid v1 to GridLegacy and upgrade Grid2 to Grid
npx @mui/codemod v7.0.0/grid-props <path/to/folder>
# Update InputLabel size="normal" to "medium"
npx @mui/codemod v7.0.0/input-label-size-normal-medium <path/to/folder>
# Move graduated components from @mui/lab to @mui/material
npx @mui/codemod v7.0.0/lab-removed-components <path/to/folder>
21. Design Best Practices
-
Always use
CssBaseline— Normalizes browser defaults and applies theme background/text colors. -
Prefer
sxfor one-off styles — Use thesxprop instead of inlinestyle. It's theme-aware and supports responsive values. -
Use
styled()for reusable styled components — Create shared styled components for consistency. -
Use theme
componentsfor global overrides — Set default props and styles at the theme level to maintain consistency across your app. -
Responsive from the start — Use responsive
sxobjects or theGridcomponent with breakpoint-based sizes. -
Enable CSS variables for dark mode — Set
cssVariables: trueandcolorSchemes: { dark: true }to avoid SSR flicker and enable seamless toggling. -
Use
theme.applyStyles('dark', {...})— Instead of checkingtheme.palette.modefor dark mode styles. -
Import components individually — Use
import Button from '@mui/material/Button'instead of barrel imports for smaller bundles. -
Leverage MUI
colors— Use pre-built color palettes from@mui/material/colorsfor quick prototyping. -
Use semantic Typography — Map variants to proper HTML elements with the
componentprop for better accessibility. -
Don't override with
!important— Use the theme'sstyleOverridesorsxarray syntax for specificity instead. -
Use
Stackfor 1D layout,Gridfor 2D — Stack for rows/columns of items, Grid for complex dashboard-like layouts. -
Use
slots/slotPropsfor component customization — The standardized v7 API replacing the oldInputProps,componentsProps, etc. -
Use
theme.vars.*for CSS variable references — When CSS variables are enabled, prefertheme.vars.palette.primary.mainovertheme.palette.primary.mainfor better dark mode performance. -
Enable CSS layers for Tailwind integration — Use
enableCssLayeronStyledEngineProviderto avoid specificity conflicts.
22. Gradients (Linear, Radial, Conic)
MUI supports standard CSS gradients in both the sx prop and the styled() API.
Linear Gradients
<Box
sx={{
background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
height: 100,
borderRadius: 1,
}}
/>
Radial & Conic Gradients
<Box
sx={{
background: 'radial-gradient(circle, rgba(63,94,251,1) 0%, rgba(252,70,107,1) 100%)',
height: 150,
}}
/>
<Box
sx={{
background: 'conic-gradient(from 0deg, red, yellow, green, blue, red)',
width: 200,
height: 200,
borderRadius: '50%',
}}
/>
Theme-Aware Gradients (using palette variables)
If cssVariables: true is enabled:
<Box
sx={{
background: (theme) =>
`linear-gradient(to right, ${theme.vars.palette.primary.main}, ${theme.vars.palette.secondary.main})`,
}}
/>
23. 3D Transforms
Use 3D transform properties directly within the sx prop or styled() function.
<Box
sx={{
perspective: "1000px",
"&:hover .inner": {
transform: "rotateY(180deg)",
},
}}
>
<Box
className="inner"
sx={{
width: 200,
height: 200,
transition: "transform 0.6s",
transformStyle: "preserve-3d",
position: "relative",
"& > div": {
position: "absolute",
width: "100%",
height: "100%",
backfaceVisibility: "hidden",
},
}}
>
<Box sx={{ bgcolor: "primary.main" }}>Front</Box>
<Box sx={{ bgcolor: "secondary.main", transform: "rotateY(180deg)" }}>
Back
</Box>
</Box>
</Box>
sx Property |
Description |
|---|---|
perspective |
Distance from viewer to z=0 plane |
transformStyle |
Specify if children are 3D (preserve-3d) |
backfaceVisibility |
Show/hide back of element (hidden, visible) |
rotateX, rotateY |
3D rotations |
translateZ |
Z-axis translation |
24. Logical Properties (v6+ Support)
MUI v6 fully supports CSS Logical Properties in the sx prop. These properties adapt to the writing mode (LTR vs. RTL) automatically.
| Logical Property | Physical Mapping (LTR) | MUI Spacing Support |
|---|---|---|
paddingBlock |
padding-top/bottom |
✅ (theme.spacing) |
paddingInline |
padding-left/right |
✅ (theme.spacing) |
marginBlock |
margin-top/bottom |
✅ (theme.spacing) |
marginInline |
margin-left/right |
✅ (theme.spacing) |
blockSize |
height |
✅ |
inlineSize |
width |
✅ |
insetBlock |
top/bottom |
✅ |
insetInline |
left/right |
✅ |
Usage Example
<Box
sx={{
marginBlock: 2, // margin-top & margin-bottom: 16px
paddingInline: 4, // padding-left & padding-right: 32px
inlineSize: "100%", // width: 100%
borderInlineStart: (theme) => `4px solid ${theme.palette.primary.main}`,
}}
>
Internationalization-ready container
</Box>
25. Text Shadows & Masks
Text Shadows
<Typography
variant="h2"
sx={{
textShadow: "2px 2px 4px rgba(0,0,0,0.3)",
fontWeight: "bold",
}}
>
Glow Effect
</Typography>
Masks
Use maskImage (or -webkit-mask-image) to hide parts of an element.
<Box
component="img"
src="photo.jpg"
sx={{
width: 300,
maskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
WebkitMaskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
}}
/>
26. Advanced States & Variants (Pseudo-classes)
Beyond hover and focus, the sx prop supports all CSS pseudo-selectors.
<Box
sx={{
// Select every second child
"& > *:nth-of-type(2n)": {
bgcolor: "action.hover",
},
// Target everything EXCEPT the first child
"& > *:not(:first-of-type)": {
mt: 1,
},
// Only child styles
"&:only-of-type": {
border: "2px dashed blue",
},
// Disabled state (for inputs/buttons)
"&.Mui-disabled": {
opacity: 0.5,
pointerEvents: "none",
},
}}
>
{/* Children */}
</Box>
27. MUI System Deep Dive
For building custom component libraries or low-level layout primitives, use @mui/system directly.
createBox & createStack
Allows creating custom Box/Stack versions with restricted or expanded system props.
import { createBox } from '@mui/system';
const MyBox = createBox({
defaultStates: { ... },
defaultTheme: customTheme,
});
styled with custom components
When wrapping non-MUI components to give them system support:
import { styled } from "@mui/material/styles";
const MyComponent = ({ className }) => <div className={className}>...</div>;
const StyledMyComponent = styled(MyComponent)({
display: "flex",
padding: 16,
});
The shouldForwardProp Trick
Prevent custom internal props from reaching the DOM:
const MyStyledButton = styled(Button, {
shouldForwardProp: (prop) => prop !== "isFancy",
})(({ theme, isFancy }) => ({
color: isFancy ? theme.palette.primary.main : theme.palette.grey[500],
}));