--- 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'; Confirm Action Are you sure? ``` ### 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 ( ); } ``` ### 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}