---
name: slots-api
description: MUI slots and slotProps API for deep component customization — replacing internal elements, custom renderers, and composition patterns
triggers:
- slots
- slotProps
- component customization
- custom renderer
- inner elements
- PaperComponent
- PopperComponent
allowed-tools:
- Read
- Glob
- Grep
- Write
- Edit
globs:
- "*.tsx"
- "*.ts"
---
# MUI Slots & slotProps API
The slots/slotProps pattern is MUI's primary mechanism for deep component customization. It lets you replace internal sub-components, inject custom renderers, and pass props to every layer of a compound component without wrapper hacks.
## 1. What Are Slots?
Every compound MUI component is built from smaller internal elements. The `slots` prop lets you swap any of those internal elements with your own component. The `slotProps` prop lets you pass additional props to each slot — whether you replaced it or not.
```tsx
// Before (MUI v5 — deprecated)
// After (MUI v6+ — slots API)
```
**Key rules:**
- `slots` accepts component references (not JSX elements)
- `slotProps` accepts either a plain object or a callback function
- Slot names are camelCase: `slots.valueLabel`, not `slots.ValueLabel`
- The component you provide receives all the props that the default slot component would receive — spread them through
## 2. Common Slot Patterns by Component
### TextField
```tsx
import { TextField, InputBase, FormHelperText } from '@mui/material';
// Replace the underlying input element
```
### Autocomplete
```tsx
import {
Autocomplete,
TextField,
Paper,
Popper,
type PaperProps,
type PopperProps,
type AutocompleteRenderOptionState,
} from '@mui/material';
import { forwardRef } from 'react';
// Custom paper with shadow and border radius
const StyledPaper = forwardRef((props, ref) => (
));
StyledPaper.displayName = 'StyledPaper';
// Custom popper with width matching
const WidePopper = forwardRef((props, ref) => (
));
WidePopper.displayName = 'WidePopper';
}
/>
```
### Select
```tsx
import { Select, MenuItem } from '@mui/material';
```
### Slider
```tsx
import { Slider, type SliderThumbSlotProps } from '@mui/material';
import { forwardRef } from 'react';
// Custom thumb with tooltip-style display
const CustomThumb = forwardRef(
(props, ref) => {
const { children, className, ...other } = props;
return (
{children}
{props['aria-valuenow']}
);
}
);
CustomThumb.displayName = 'CustomThumb';
```
### DatePicker
```tsx
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { PickersDay, type PickersDayProps } from '@mui/x-date-pickers/PickersDay';
import { type Dayjs } from 'dayjs';
// Highlight weekends
function CustomDay(props: PickersDayProps) {
const { day, ...other } = props;
const isWeekend = day.day() === 0 || day.day() === 6;
return (
);
}
```
### Dialog
```tsx
import { Dialog, Backdrop, type BackdropProps } from '@mui/material';
import { forwardRef } from 'react';
const BlurredBackdrop = forwardRef((props, ref) => (
));
BlurredBackdrop.displayName = 'BlurredBackdrop';
```
### Tooltip
```tsx
import { Tooltip, Popper, type PopperProps } from '@mui/material';
import { forwardRef } from 'react';
const ThemedPopper = forwardRef((props, ref) => (
));
ThemedPopper.displayName = 'ThemedPopper';
```
## 3. Custom Slot Components
When creating a custom slot component, follow these rules:
1. **Forward the ref** — MUI needs refs for positioning (Popper, Tooltip) and focus management
2. **Spread all props** — The parent component passes required props (event handlers, ARIA attributes, styles); dropping them breaks functionality
3. **Use `forwardRef`** — Required for all slot components
```tsx
import { forwardRef, type HTMLAttributes } from 'react';
import { Paper, type PaperProps } from '@mui/material';
// CORRECT: Forward ref, spread props, then add customizations
const CustomPaper = forwardRef((props, ref) => {
const { children, sx, ...rest } = props;
return (
{children}
);
});
CustomPaper.displayName = 'CustomPaper';
// Usage
}
/>
```
**Merging `sx` correctly:**
When your custom slot defines its own `sx` and you also want `slotProps.*.sx` to apply, merge them using the array syntax:
```tsx
const MySlot = forwardRef(({ sx, ...props }, ref) => (
));
```
## 4. slotProps Callback Form
Instead of a static object, pass a function to `slotProps` to compute props based on the component's current state (the "owner state"):
```tsx
import { TextField } from '@mui/material';
({
sx: {
fontWeight: ownerState.focused ? 700 : 400,
bgcolor: ownerState.error ? 'error.light' : 'transparent',
transition: 'all 0.2s ease',
},
}),
inputLabel: (ownerState) => ({
sx: {
color: ownerState.focused ? 'primary.main' : 'text.secondary',
fontSize: ownerState.shrink ? 12 : 16,
},
}),
}}
/>
```
The callback pattern is especially useful for:
- **Conditional styling** based on focused/error/disabled state
- **Computed ARIA attributes** based on value
- **Dynamic classes** that depend on the component's internal state
```tsx
import { Slider } from '@mui/material';
({
sx: {
// Change thumb color at extremes
bgcolor: ownerState.value === 100
? 'success.main'
: ownerState.value === 0
? 'error.main'
: 'primary.main',
width: ownerState.active ? 28 : 20,
height: ownerState.active ? 28 : 20,
transition: 'width 0.1s, height 0.1s',
},
}),
valueLabel: (ownerState) => ({
sx: {
bgcolor: ownerState.value > 80 ? 'success.dark' : 'primary.dark',
},
}),
}}
/>
```
### Owner State Properties
Common ownerState properties vary by component:
| Component | ownerState properties |
|-----------|----------------------|
| TextField | `focused`, `error`, `disabled`, `required`, `filled`, `size`, `variant`, `color` |
| Slider | `active`, `disabled`, `dragging`, `focusedThumbIndex`, `marked`, `orientation`, `value` |
| Button | `active`, `disabled`, `focusVisible`, `fullWidth`, `size`, `variant`, `color` |
| Chip | `clickable`, `deletable`, `disabled`, `size`, `variant`, `color` |
| Badge | `anchorOrigin`, `color`, `invisible`, `max`, `overlap`, `variant` |
## 5. Migration from components/componentsProps (v5 to v6)
MUI v6 unified the customization API. Here is the mapping:
### Prop Renames
```tsx
// v5 (deprecated)
// v6+ (current)
```
### Named Props to Slots
Some components had dedicated props that are now unified under `slots`:
| v5 Prop | v6 Equivalent |
|---------|---------------|
| `PaperComponent` | `slots.paper` |
| `PopperComponent` | `slots.popper` |
| `TransitionComponent` | `slots.transition` |
| `BackdropComponent` | `slots.backdrop` |
| `IconComponent` | `slots.openPickerIcon` |
| `OpenPickerButtonProps` | `slotProps.openPickerButton` |
| `InputAdornmentProps` | `slotProps.inputAdornment` |
| `ToolbarComponent` | `slots.toolbar` |
| `PaperProps` | `slotProps.paper` |
| `PopperProps` | `slotProps.popper` |
| `TransitionProps` | `slotProps.transition` |
| `BackdropProps` | `slotProps.backdrop` |
### Casing Change
`components` used PascalCase keys; `slots` uses camelCase:
```tsx
// v5
components={{ Toolbar: CustomToolbar, NoRowsOverlay: Empty }}
// v6
slots={{ toolbar: CustomToolbar, noRowsOverlay: Empty }}
```
### Codemod
MUI provides a codemod to automate migration:
```bash
npx @mui/codemod v6.0.0/preset-safe ./src
```
## 6. DataGrid Slots (Comprehensive)
The DataGrid has the most extensive slots API in MUI. Here is a thorough reference:
### Toolbar
```tsx
import {
DataGrid,
GridToolbarContainer,
GridToolbarExport,
GridToolbarFilterButton,
GridToolbarQuickFilter,
type GridSlots,
} from '@mui/x-data-grid';
function CustomToolbar() {
return (
}
onClick={handleAddRow}
>
Add Row
);
}
```
### Footer
```tsx
import { GridFooterContainer, type GridSlotsComponentsProps } from '@mui/x-data-grid';
function CustomFooter(props: NonNullable) {
const { selectedRowCount, totalRowCount } = props;
return (
{selectedRowCount > 0
? `${selectedRowCount} of ${totalRowCount} selected`
: `${totalRowCount} total rows`}
);
}
```
### No Rows & Loading Overlays
```tsx
function NoRowsOverlay() {
return (
No results found
Try adjusting your search or filter criteria
);
}
function LoadingOverlay() {
return (
Loading data...
);
}
```
### Custom Cell Renderer via renderCell vs Slots
For per-column cell customization, use `renderCell` on the column definition. For global cell wrapper customization, use slots:
```tsx
import {
type GridColDef,
type GridRenderCellParams,
type GridCellParams,
} from '@mui/x-data-grid';
// Per-column: renderCell
const columns: GridColDef[] = [
{
field: 'status',
headerName: 'Status',
width: 150,
renderCell: (params: GridRenderCellParams) => (
),
},
{
field: 'progress',
headerName: 'Progress',
width: 200,
renderCell: (params: GridRenderCellParams) => (
{params.value}%
),
},
{
field: 'actions',
headerName: 'Actions',
type: 'actions',
width: 120,
getActions: (params) => [
}
label="Edit"
onClick={() => handleEdit(params.id)}
/>,
}
label="Delete"
onClick={() => handleDelete(params.id)}
showInMenu
/>,
],
},
];
```
### Column Menu
```tsx
import {
GridColumnMenu,
type GridColumnMenuProps,
GridColumnMenuFilterItem,
GridColumnMenuSortItem,
GridColumnMenuColumnsItem,
} from '@mui/x-data-grid';
function CustomColumnMenu(props: GridColumnMenuProps) {
return (
);
}
```
### Base Component Overrides
DataGrid allows overriding the base MUI components it uses internally:
```tsx
```
### Complete DataGrid Slot Reference
| Slot | Purpose |
|------|---------|
| `toolbar` | Top toolbar area |
| `footer` | Bottom footer area |
| `columnMenu` | Column header dropdown menu |
| `columnHeaders` | Column header row container |
| `cell` | Individual cell wrapper |
| `row` | Row wrapper |
| `noRowsOverlay` | Shown when rows array is empty |
| `noResultsOverlay` | Shown when filtering returns no results |
| `loadingOverlay` | Shown while `loading={true}` |
| `detailPanelContent` | Row detail expand panel (Pro) |
| `detailPanelExpandIcon` | Expand icon for detail panel (Pro) |
| `detailPanelCollapseIcon` | Collapse icon for detail panel (Pro) |
| `pagination` | Pagination controls |
| `filterPanel` | Filter panel |
| `columnsPanel` | Columns visibility panel |
| `preferencesPanel` | Settings panel wrapper |
| `baseButton` | Base Button used across the grid |
| `baseTextField` | Base TextField used across the grid |
| `baseSelect` | Base Select used across the grid |
| `baseCheckbox` | Base Checkbox used across the grid |
## 7. TypeScript Typing
### Typing Custom Slot Components
Use `SlotComponentProps` to correctly type a custom slot:
```tsx
import { type SlotComponentProps } from '@mui/base/utils';
import { type PaperProps } from '@mui/material/Paper';
import { type AutocompleteOwnerState } from '@mui/material/Autocomplete';
import { forwardRef } from 'react';
// Method 1: Use the exact MUI prop type directly
const TypedPaper = forwardRef((props, ref) => (
));
TypedPaper.displayName = 'TypedPaper';
// Method 2: Use SlotComponentProps for full owner state access
type AutocompletePaperSlotProps = SlotComponentProps<
typeof Paper,
{}, // additional props you want
AutocompleteOwnerState
>;
const TypedPaperWithOwnerState = forwardRef(
(props, ref) => {
const { ownerState, ...rest } = props;
return (
);
}
);
TypedPaperWithOwnerState.displayName = 'TypedPaperWithOwnerState';
```
### Typing slotProps Callbacks
```tsx
import { type TextFieldOwnerState } from '@mui/material/TextField';
import { type SliderOwnerState } from '@mui/material/Slider';
// The callback signature is (ownerState: OwnerState) => SlotProps
({
sx: {
bgcolor: ownerState.error ? 'error.light' : 'background.paper',
},
}),
}}
/>
({
style: {
backgroundColor: ownerState.active ? '#ff0000' : '#1976d2',
},
}),
}}
/>
```
### Typing DataGrid Slots
```tsx
import {
DataGrid,
type GridSlots,
type GridSlotsComponentsProps,
type GridToolbarContainerProps,
} from '@mui/x-data-grid';
// Custom toolbar with typed props
interface CustomToolbarProps extends GridToolbarContainerProps {
onAddClick: () => void;
title: string;
}
function CustomToolbar({ onAddClick, title, ...props }: CustomToolbarProps) {
return (
{title}
);
}
// Pass custom props through slotProps
```
### Extending Slot Types for Custom Components
When building a reusable component that accepts slots itself:
```tsx
import { type ElementType, type ComponentPropsWithRef } from 'react';
// Define your own slots interface
interface MyComponentSlots {
root?: ElementType;
header?: ElementType;
content?: ElementType;
footer?: ElementType;
}
// Define slotProps to match
interface MyComponentSlotProps {
root?: ComponentPropsWithRef<'div'>;
header?: ComponentPropsWithRef<'div'> & { title?: string };
content?: ComponentPropsWithRef<'div'>;
footer?: ComponentPropsWithRef<'div'>;
}
interface MyComponentProps {
slots?: MyComponentSlots;
slotProps?: MyComponentSlotProps;
children: React.ReactNode;
}
function MyComponent({ slots, slotProps, children }: MyComponentProps) {
const Root = slots?.root ?? 'div';
const Header = slots?.header ?? 'div';
const Content = slots?.content ?? 'div';
const Footer = slots?.footer ?? 'div';
return (
{children}
);
}
```
## Quick Reference: When to Use What
| Goal | Approach |
|------|----------|
| Change props of an internal element | `slotProps.elementName` |
| Replace an internal element entirely | `slots.elementName` |
| Conditional props based on state | `slotProps.elementName` as callback |
| Custom cell in DataGrid column | `renderCell` on `GridColDef` |
| Custom toolbar/footer in DataGrid | `slots.toolbar` / `slots.footer` |
| Override base MUI components in DataGrid | `slots.baseButton` etc. |
| Style an internal element | `slotProps.elementName.sx` |
| Add test IDs to internal elements | `slotProps.elementName['data-testid']` |