material-ui icon indicating copy to clipboard operation
material-ui copied to clipboard

[RFC] The way to override components and slots

Open siriwatknp opened this issue 3 years ago • 10 comments

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).
  • slots and slotsProps same as https://github.com/mui/material-ui/issues/33416
  • slots.{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: { ... }
  }}
/>

siriwatknp avatar Sep 16 '22 07:09 siriwatknp

So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.x.component, yes?

michaldudak avatar Sep 16 '22 08:09 michaldudak

So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.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.

siriwatknp avatar Sep 16 '22 08:09 siriwatknp

Generally, I see two changes are proposed here:

  1. Change (or settle on) the interpretation of the component prop, so that it is used to customize the leaf element (similarly to the as prop).
  2. Introduce the components prop that would act as a shortcut to slotProps.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.

michaldudak avatar Sep 19 '22 13:09 michaldudak

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.

mnajdova avatar Sep 19 '22 14:09 mnajdova

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 } }} />

siriwatknp avatar Sep 20 '22 02:09 siriwatknp

Marked this as RFC for Material UI and Joy UI only.

siriwatknp avatar Sep 20 '22 03:09 siriwatknp

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.

mnajdova avatar Sep 20 '22 07:09 mnajdova

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).

michaldudak avatar Sep 20 '22 07:09 michaldudak

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.

siriwatknp avatar Sep 20 '22 07:09 siriwatknp

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.

mnajdova avatar Sep 20 '22 08:09 mnajdova

Update: Joy UI already follows this approach.

siriwatknp avatar Sep 21 '23 09:09 siriwatknp

This issue can be closed once all Material UI components support slots and slotProps. cc @DiegoAndai

siriwatknp avatar Jan 17 '24 03:01 siriwatknp

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

colangeloproductions avatar Feb 08 '24 02:02 colangeloproductions

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.

michaldudak avatar Feb 08 '24 07:02 michaldudak

Okay thats some clarity, but what about the InputCalculate that I would like render instead of the input slot from the NumberInput?

colangeloproductions avatar Feb 08 '24 07:02 colangeloproductions

What exactly do you have a problem with?

This should work:

<NumberInput slots={{ input: InputCalculate }} />

michaldudak avatar Feb 08 '24 08:02 michaldudak

@michaldudak How can we pass parameters to InputCalculate <NumberInput slots={{ input: InputCalculate }} />

poojaphapale avatar Mar 09 '24 10:03 poojaphapale

You can use slotProps:

<NumberInput slots={{ input: InputCalculate }} slotProps={{ input: { ... } }} />

michaldudak avatar Mar 12 '24 17:03 michaldudak