material-ui
material-ui copied to clipboard
[RFC] The way to override components and slots
What's the problem? 🤔
Before reading this RFC, please go through #34333 first so that we are on the same page about the meaning of components and slots.
From #33416, and #21453 it seems like we are only talking about slot override (replacing the slot). However, for Material UI and Joy UI, we also need a component override (changing the HTML tag of the slot).
What are the requirements? ❓
- How to override the HTML tag on each slot
- How to replace the slot with a custom React component
Proposed solution 🟢
My proposed solution aims for the least breaking changes. All components (MUI Base, Material UI, and Joy UI) should follow this:
Components with a single slot (root)
component(existing prop): for changing the root slot's HTML tag. (I think we should not drop this prop because it will be a huge breaking change)
~For this kind of components, it does not make sense to replace the root slot so having just component prop is cleaner.~ This is not true for MUI Base 🤔.
Components with more than one slot
component(existing prop): for changing the subcomponent of the root slot (could be an HTML tag or React element).slotsandslotsPropssame as https://github.com/mui/material-ui/issues/33416slots.{slot}.component: for changing the subcomponent of the target slot (could be an HTML tag or React element).
To replace the HTML of the slot, use slotsProps={{ listbox: { component: 'div' } }}.
Components that have nested components
See the problem
Flatten the nested component slots with a new name. For example, TextField has Input as a nested component could look something like this:
<TextField
slotsProps={{
root: {...},
inputRoot: { ... },
inputInput: { ... },
helperText: { ... }
}}
/>
So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.x.component, yes?
So, if I understand this correctly, you're proposing that the
components.xis a shorter way of writingslotProps.x.component, yes?
hmm, I forgot that we can use component inside slotsProps:
<Select
slotProps={{
listbox: {
component: 'div',
}
}}
/>
This sounds better than having another components prop.
Generally, I see two changes are proposed here:
- Change (or settle on) the interpretation of the
componentprop, so that it is used to customize the leaf element (similarly to theasprop). - Introduce the
componentsprop that would act as a shortcut toslotProps.x.component.
Do I understand the intent correctly?
Assuming so, this proposal would only make sense in styled libraries (Material UI and Joy UI). In MUI Base, as explained in https://github.com/mui/material-ui/issues/34333#issuecomment-1251007834, the component is almost exclusively a DOM node, so it does not have the as prop.
That being said, if we decide to go with option 1 I listed above, the component prop would be unnecessary in MUI Base (as was already proposed in https://github.com/mui/material-ui/issues/28189). We could leave it as is (as an alias for slots.root), but it would have a different meaning in styled vs unstyled libraries.
Just clarifying that I understand the proposal correctly. We would have slots and slotsProps in Mui Base, Material UI and Joy UI, that would behave the same (replace completely the slot that is being rendered). For e.g.:
const Root = slots.root ?? DefaultRootComponent;
Apart from this, in the components inside Material UI and Joy UI, there would be a component prop which is basically an alias for the as prop in emotion (so that when it is used, all styles that were previously defined will still be applied). As all slots would be compossible components, we should already have the support for the component prop there, so we should need only one component prop per component.
We don't really need this in Mui Base, as the behavior is the same as slots.root as we don't have any styles, but that is a different discussion, already linked in https://github.com/mui/material-ui/issues/34334#issuecomment-1251043750.
Btw, this is how it already works in Material UI, so I don't expect big change anyway, unless I am misunderstanding the proposal.
so we should need only one component prop per component.
Yes, the component prop can be considered as a shortcut for slotsProps.component. If both are provided, slotsProps.component should have higher priority. cc @michaldudak
Btw, this is how it already works in Material UI, so I don't expect big change anyway, unless I am misunderstanding the proposal.
I'd say that I expect big change for Material UI because most components are using another pattern. e.g. Accordion has TransitionComponent and TransitionProps. This will be breaking changes:
// current
<Accordion TransitionComponent={Slide} TransitionProps={{ delay: 100 }} />
// new
<Accordion slots={{ transition: Slide }} slotsProps={{ transition: { delay: 100 } }} />
Marked this as RFC for Material UI and Joy UI only.
I'd say that I expect big change for Material UI because most components are using another pattern. e.g. Accordion has TransitionComponent and TransitionProps. This will be breaking changes:
// current <Accordion TransitionComponent={Slide} TransitionProps={{ delay: 100 }} /> // new <Accordion slots={{ transition: Slide }} slotsProps={{ transition: { delay: 100 } }} />
What I meant is that in terms of behavior it will behave the same. For the props surface, we could support both for smoother migration and provide codemods if people want to migrate to the new paradigm sooner.
What I meant is that in terms of behavior it will behave the same.
Not necessarily. The current pattern in Material UI uses the as prop to modify the leaf component:
const AutocompletePaper = styled(Paper, { /* ... */ });
/* ... */
<AutocompletePaper
ownerState={ownerState}
as={PaperComponent}
{...componentsProps.paper}
className={clsx(classes.paper, componentsProps.paper?.className)}
>
whereas the slots prop would replace the whole slot (unless it has a different behavior in styled vs unstyled libraries).
For the props surface, we could support both for smoother migration and provide codemods if people want to migrate to the new paradigm sooner
Yep, agree. We can start introducing slots and slotsProps in v5 with a codemod and then deprecate the existing props, e.g. TransitionComponent.
whereas the slots prop would replace the whole slot (unless it has a different behavior in styled vs unstyled libraries).
Yep, component props is "as" and the slot props behave the same as the component props we have on some components, for example TransitionComponent, PopperComponent etc.
Update: Joy UI already follows this approach.
This issue can be closed once all Material UI components support slots and slotProps. cc @DiegoAndai
Hi there,
I've been trying to understand the possibilities of the slots and slotProps with help of:
But I'm not sure its capable of what I'm trying to do.. maybe someone could give some advice?
I have a React Component thats based on the MUI Base Input -> which is InputCalculate (Math operations inside of Input field)
Now with the NumberInput that has been released I've been trying to use the slots prop to render my InputCalculate instead of the default.
Can anyone advise me how I can render a complete different HTML structure / Component and also give it props? The docs I've read are giving me the impression I can only change the HTML Tag (ol to ul, button to a etc.) and css styles.
Cheers Romeo
You can pass in props using the slotProps prop. Unfortunately, due to performance issues with TypeScript, if you pass in a custom slot component, the corresponding slotProps won't have a related type. So if you do slots={{ root: MyFancyComponent }}, the slotProps.root won't expect the props of MyFancyComponent and some casting may be necessary.
We are aware of the limitation of this API, and are currently working on improvements in this area. We plan to post an RFC soon and flesh out all the details of the new API.
Okay thats some clarity, but what about the InputCalculate that I would like render instead of the input slot from the NumberInput?
What exactly do you have a problem with?
This should work:
<NumberInput slots={{ input: InputCalculate }} />
@michaldudak How can we pass parameters to InputCalculate <NumberInput slots={{ input: InputCalculate }} />
You can use slotProps:
<NumberInput slots={{ input: InputCalculate }} slotProps={{ input: { ... } }} />