| name | material-ui |
| description | Builds React applications with Material UI (MUI) components implementing Material Design. Use when creating modern UIs with Google's design system, custom theming, or accessible form controls. |
Material UI (MUI)
React component library implementing Google's Material Design with extensive customization.
Quick Start
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Button from '@mui/material/Button';
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Button variant="contained">Hello World</Button>
</ThemeProvider>
);
}
Core Components
Button
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import LoadingButton from '@mui/lab/LoadingButton';
import DeleteIcon from '@mui/icons-material/Delete';
import SendIcon from '@mui/icons-material/Send';
// Variants
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
// Colors
<Button color="primary">Primary</Button>
<Button color="secondary">Secondary</Button>
<Button color="success">Success</Button>
<Button color="error">Error</Button>
// Sizes
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
<Button size="large">Large</Button>
// With icons
<Button variant="contained" startIcon={<SendIcon />}>Send</Button>
<Button variant="outlined" endIcon={<DeleteIcon />}>Delete</Button>
// Icon button
<IconButton color="primary" aria-label="delete">
<DeleteIcon />
</IconButton>
// Loading button
<LoadingButton loading variant="contained">Submit</LoadingButton>
<LoadingButton loading loadingPosition="start" startIcon={<SaveIcon />}>
Save
</LoadingButton>
TextField
import TextField from '@mui/material/TextField';
import InputAdornment from '@mui/material/InputAdornment';
import Visibility from '@mui/icons-material/Visibility';
// Variants
<TextField label="Outlined" variant="outlined" />
<TextField label="Filled" variant="filled" />
<TextField label="Standard" variant="standard" />
// States
<TextField label="Required" required />
<TextField label="Disabled" disabled />
<TextField label="Error" error helperText="Invalid input" />
// Types
<TextField label="Password" type="password" />
<TextField label="Number" type="number" />
<TextField label="Multiline" multiline rows={4} />
// With adornments
<TextField
label="Amount"
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
}}
/>
<TextField
label="Password"
type={showPassword ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleClickShowPassword}>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
Select
import { FormControl, InputLabel, Select, MenuItem } from '@mui/material';
// Basic select
<FormControl fullWidth>
<InputLabel id="demo-label">Age</InputLabel>
<Select
labelId="demo-label"
id="demo-select"
value={age}
label="Age"
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
// Multiple select
<Select
multiple
value={personName}
onChange={handleChange}
renderValue={(selected) => selected.join(', ')}
>
{names.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={personName.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
Dialog
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
} from '@mui/material';
function AlertDialog() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outlined" onClick={() => setOpen(true)}>
Open Dialog
</Button>
<Dialog
open={open}
onClose={() => setOpen(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
Confirm Delete
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete this item?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={handleDelete} autoFocus color="error">
Delete
</Button>
</DialogActions>
</Dialog>
</>
);
}
Card
import {
Card,
CardMedia,
CardContent,
CardActions,
Typography,
Button,
} from '@mui/material';
<Card sx={{ maxWidth: 345 }}>
<CardMedia
component="img"
height="140"
image="/image.jpg"
alt="Product"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
Product Name
</Typography>
<Typography variant="body2" color="text.secondary">
Product description goes here. This is a brief overview.
</Typography>
</CardContent>
<CardActions>
<Button size="small">Share</Button>
<Button size="small">Learn More</Button>
</CardActions>
</Card>
Snackbar
import { Snackbar, Alert, Button } from '@mui/material';
function SnackbarDemo() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Show Snackbar</Button>
<Snackbar
open={open}
autoHideDuration={6000}
onClose={() => setOpen(false)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert
onClose={() => setOpen(false)}
severity="success"
variant="filled"
sx={{ width: '100%' }}
>
Operation completed successfully!
</Alert>
</Snackbar>
</>
);
}
// Severity options: success, info, warning, error
Tabs
import { Tabs, Tab, Box } from '@mui/material';
function TabPanel({ children, value, index }) {
return (
<div hidden={value !== index} role="tabpanel">
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
</div>
);
}
function TabsDemo() {
const [value, setValue] = useState(0);
return (
<Box sx={{ width: '100%' }}>
<Tabs value={value} onChange={(e, newValue) => setValue(newValue)}>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
<TabPanel value={value} index={0}>Content One</TabPanel>
<TabPanel value={value} index={1}>Content Two</TabPanel>
<TabPanel value={value} index={2}>Content Three</TabPanel>
</Box>
);
}
Menu
import { Menu, MenuItem, Button, ListItemIcon, ListItemText } from '@mui/material';
import ContentCut from '@mui/icons-material/ContentCut';
import ContentCopy from '@mui/icons-material/ContentCopy';
import ContentPaste from '@mui/icons-material/ContentPaste';
function MenuDemo() {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
return (
<>
<Button
onClick={(e) => setAnchorEl(e.currentTarget)}
aria-controls={open ? 'menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
>
Open Menu
</Button>
<Menu
id="menu"
anchorEl={anchorEl}
open={open}
onClose={() => setAnchorEl(null)}
>
<MenuItem onClick={() => setAnchorEl(null)}>
<ListItemIcon><ContentCut fontSize="small" /></ListItemIcon>
<ListItemText>Cut</ListItemText>
</MenuItem>
<MenuItem onClick={() => setAnchorEl(null)}>
<ListItemIcon><ContentCopy fontSize="small" /></ListItemIcon>
<ListItemText>Copy</ListItemText>
</MenuItem>
<MenuItem onClick={() => setAnchorEl(null)}>
<ListItemIcon><ContentPaste fontSize="small" /></ListItemIcon>
<ListItemText>Paste</ListItemText>
</MenuItem>
</Menu>
</>
);
}
The sx Prop
MUI's styling shorthand:
import { Box, Button } from '@mui/material';
<Box
sx={{
// Spacing (theme.spacing * n)
p: 2, // padding: 16px
m: 1, // margin: 8px
px: 3, // paddingX: 24px
my: 'auto', // marginY: auto
// Colors from theme
bgcolor: 'primary.main',
color: 'text.secondary',
// Sizing
width: 300,
maxWidth: '100%',
height: 'auto',
// Flexbox
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
// Border
border: 1,
borderColor: 'divider',
borderRadius: 2,
// Typography
fontSize: 14,
fontWeight: 'bold',
textAlign: 'center',
// Shadows
boxShadow: 3,
// Responsive
width: { xs: '100%', sm: 400, md: 500 },
// Pseudo-classes
'&:hover': {
bgcolor: 'primary.dark',
},
// Nested selectors
'& .child': {
color: 'secondary.main',
},
}}
>
Content
</Box>
Theming
Custom Theme
import { createTheme, ThemeProvider } from '@mui/material/styles';
const theme = createTheme({
palette: {
mode: 'light', // or 'dark'
primary: {
main: '#1976d2',
light: '#42a5f5',
dark: '#1565c0',
contrastText: '#fff',
},
secondary: {
main: '#9c27b0',
},
error: {
main: '#d32f2f',
},
warning: {
main: '#ed6c02',
},
info: {
main: '#0288d1',
},
success: {
main: '#2e7d32',
},
background: {
default: '#f5f5f5',
paper: '#fff',
},
text: {
primary: 'rgba(0, 0, 0, 0.87)',
secondary: 'rgba(0, 0, 0, 0.6)',
disabled: 'rgba(0, 0, 0, 0.38)',
},
},
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 600,
},
h2: {
fontSize: '2rem',
fontWeight: 600,
},
button: {
textTransform: 'none', // Disable uppercase
},
},
shape: {
borderRadius: 8,
},
spacing: 8, // Default spacing unit
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
padding: '8px 24px',
},
contained: {
boxShadow: 'none',
'&:hover': {
boxShadow: 'none',
},
},
},
defaultProps: {
disableElevation: true,
},
},
MuiTextField: {
defaultProps: {
variant: 'outlined',
size: 'small',
},
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 12,
},
},
},
},
});
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
Dark Mode
import { createTheme, ThemeProvider } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useMemo, useState } from 'react';
function App() {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = useState(prefersDarkMode ? 'dark' : 'light');
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
...(mode === 'dark'
? {
background: {
default: '#121212',
paper: '#1e1e1e',
},
}
: {}),
},
}),
[mode]
);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
Toggle Theme
</Button>
</ThemeProvider>
);
}
useTheme Hook
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
function MyComponent() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
return (
<Box
sx={{
backgroundColor: theme.palette.primary.main,
padding: theme.spacing(2),
}}
>
{isMobile ? 'Mobile View' : 'Desktop View'}
</Box>
);
}
Layout
Box
import Box from '@mui/material/Box';
<Box
component="section"
sx={{ p: 2, border: '1px solid grey' }}
>
Content
</Box>
Container
import Container from '@mui/material/Container';
<Container maxWidth="sm">Small container</Container>
<Container maxWidth="md">Medium container</Container>
<Container maxWidth="lg">Large container</Container>
<Container maxWidth="xl">Extra large container</Container>
Grid
import Grid from '@mui/material/Grid';
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4}>
<Item>xs=12 sm=6 md=4</Item>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Item>xs=12 sm=6 md=4</Item>
</Grid>
<Grid item xs={12} sm={12} md={4}>
<Item>xs=12 sm=12 md=4</Item>
</Grid>
</Grid>
Stack
import Stack from '@mui/material/Stack';
<Stack direction="row" spacing={2}>
<Item>Item 1</Item>
<Item>Item 2</Item>
<Item>Item 3</Item>
</Stack>
<Stack
direction={{ xs: 'column', sm: 'row' }}
spacing={{ xs: 1, sm: 2, md: 4 }}
justifyContent="center"
alignItems="center"
>
<Item>Item 1</Item>
<Item>Item 2</Item>
</Stack>
Forms with React Hook Form
import { useForm, Controller } from 'react-hook-form';
import { TextField, Button, Box } from '@mui/material';
function Form() {
const { control, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => console.log(data);
return (
<Box component="form" onSubmit={handleSubmit(onSubmit)} sx={{ mt: 3 }}>
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: 'Email is required',
pattern: {
value: /^\S+@\S+$/i,
message: 'Invalid email',
},
}}
render={({ field }) => (
<TextField
{...field}
label="Email"
fullWidth
error={!!errors.email}
helperText={errors.email?.message}
margin="normal"
/>
)}
/>
<Button type="submit" fullWidth variant="contained" sx={{ mt: 3 }}>
Submit
</Button>
</Box>
);
}
Best Practices
- Use sx prop - Cleaner than inline styles, theme-aware
- ThemeProvider at root - Ensures consistent theming
- CssBaseline - Normalize styles across browsers
- Component overrides - Customize via theme.components
- Responsive breakpoints - Use theme.breakpoints helpers
Reference Files
- references/components.md - Complete component list
- references/theming.md - Advanced theming patterns