Custom slots and subcomponents
Learn how to override parts of the MUI X components.
What is a slot?
A slot is a part of a component that can be overridden and/or customized.
Some of those slots allow you to provide your own UI primitives to the MUI X components.
This is the role of all the baseXXX
component on the DataGrid
component (baseButton
, baseSelect
, ...).
These slots receive props that should be as generic as possible so that it is easy to interface any other design system.
Other slots allow you to override parts of the MUI X UI components with a custom UI built specifically for this component.
This is the role of slots like calendarHeader
on the DateCalendar
component or item
on the RichTreeView
component.
These slots receive props specific to this part of the UI and will most likely not be re-use throughout your application.
Basic usage
How to override a slot?
You can override a slot by providing a custom component to the slots
prop:
How to customize a slot?
You can pass props to any slot using the slotProps
prop:
You can also use both slots
and slotProps
on the same component:
Most slots also support a callback version of slotProps
.
This callback receives an object that contains information about the current state of the component,
that information can vary depending on the slot being used:
Correct usage
A slot is a React component; therefore, it should keep the same JavaScript reference between two renders.
If the JavaScript reference of component changes between two renders, React will remount it.
You can avoid it by not inlining the component definition in the slots
prop.
The first two examples below are buggy because the calendar header will remount after each keystroke, leading to a loss of focus.
// ❌ The `calendarHeader` slot is re-defined each time the parent component renders,
// causing the component to remount.
function MyApp() {
const [name, setName] = React.useState('');
return (
<DateCalendar
slots={{
calendarHeader: () => (
<input value={name} onChange={(event) => setName(event.target.value)} />
),
}}
/>
);
}
// ❌ The `calendarHeader` slot is re-defined each time `name` is updated,
// causing the component to remount.
function MyApp() {
const [name, setName] = React.useState('');
const CustomCalendarHeader = React.useCallback(
() => <input value={name} onChange={(event) => setName(event.target.value)} />,
[name],
);
return <DateCalendar slots={{ calendarHeader: CustomCalendarHeader }} />;
}
// ✅ The `calendarHeader` slot is defined only once, it will never remount.
const CustomCalendarHeader = ({ name, setName }) => (
<input value={name} onChange={(event) => setName(event.target.value)} />
);
function MyApp() {
const [name, setName] = React.useState('');
return (
<DateCalendar
slots={{ calendarHeader: CustomCalendarHeader }}
slotProps={{ calendarHeader: { name, setName } }}
/>
);
}
Usage with TypeScript
Type custom slots
If you want to ensure type safety on your custom slot component,
you can declare your component using the PropsFromSlot
interface:
function CustomCalendarHeader({
currentMonth,
}: PropsFromSlot<DateCalendarSlots<Dayjs>['calendarHeader']>) {
return <div>{currentMonth?.format('MM-DD-YYYY')}</div>;
}
Using additional props
If you are passing additional props to your slot, you can add them to the props your custom component receives:
interface CustomCalendarHeaderProps
extends PropsFromSlot<DateCalendarSlots<Dayjs>['calendarHeader']> {
displayWeekNumber: boolean;
setDisplayWeekNumber: (displayWeekNumber: boolean) => void;
}
You can then use these props in your custom component and access both the props provided by the host component and the props you added:
function CustomCalendarHeader({
displayWeekNumber,
setDisplayWeekNumber,
...other
}: CustomCalendarHeaderProps) {
return (
<Stack>
<DisplayWeekNumberToggle
value={displayWeekNumber}
onChange={setDisplayWeekNumber}
/>
<PickersCalendarHeader {...other} />
</Stack>
);
}
If your custom component has a different type than the default one, you will need to cast it to the correct type.
This can happen if you pass additional props to your custom component using slotProps
.
If we take the example of the calendarHeader
slot, you can cast your custom component as below:
function MyApp() {
const [displayWeekNumber, setDisplayWeekNumber] = React.useState(false);
return (
<DateCalendar
displayWeekNumber={displayWeekNumber}
slots={{
calendarHeader:
CustomCalendarHeader as DateCalendarSlots<Dayjs>['calendarHeader'],
}}
slotProps={{
calendarHeader: {
displayWeekNumber,
setDisplayWeekNumber,
} as DateCalendarSlotProps<Dayjs>['calendarHeader'],
}}
/>
);
}
Using module augmentation
If you are using one of the data grid packages, you can also use module augmentation to let TypeScript know about your custom props:
declare module '@mui/x-data-grid' {
interface ToolbarPropsOverrides {
name: string;
setName: (name: string) => void;
}
}
You can then use your custom slot without any type casting:
function CustomToolbar({ name, setName }: PropsFromSlot<GridSlots['toolbar']>) {
return <input value={name} onChange={(event) => setName(event.target.value)} />;
}
function MyApp() {
const [name, setName] = React.useState('');
return (
<DataGrid
rows={[]}
columns={[]}
slots={{ toolbar: CustomToolbar }}
slotProps={{
toolbar: { name, setName },
}}
/>
);
}
See Data Grid - Custom slots and subcomponents—Custom slot props with TypeScript for more details.
Slots of the X components
You can find the list of slots in the API documentation of each component (e.g. DataGrid, DatePicker, BarChart or RichTreeView).
Most of the slots have a standalone doc example to show how to use them: