Skip to content

Commit

Permalink
[blog] Introducing callback support in style overrides (mui#30668)
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp authored and wladimirguerra committed Feb 2, 2022
1 parent 0d312a3 commit 29f02a0
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 14 deletions.
7 changes: 7 additions & 0 deletions docs/pages/blog/callback-support-in-style-overrides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';
import TopLayoutBlog from 'docs/src/modules/components/TopLayoutBlog';
import { docs } from './callback-support-in-style-overrides.md?@mui/markdown';

export default function Page() {
return <TopLayoutBlog docs={docs} />;
}
194 changes: 194 additions & 0 deletions docs/pages/blog/callback-support-in-style-overrides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
title: Introducing callback support in style overrides
description: We're excited to introduce callback support for global theme overrides in this minor version update!
date: 2022-01-31T00:00:00.000Z
authors: ['siriwatknp']
tags: ['MUI Core', 'News']
card: false
---

<span class="x x-first x-last">[</span>MUI Core v5.3.0](https://github.com/mui-org/material-ui/releases/tag/v5.3.0) introduces the ability to write a callback in style overrides (global theming), giving you full control of component customization at the theme level.

Why is using a callback better than the existing plain object? Let me explain from the beginning<span class="x x-first x-last">…</span>

## The problems

In v4, the style engine library was JSS which had some limitations.
Style overrides were not able to support dynamic props via a callback so we relied on using classes. Take a look at the [`Chip` classes](https://github.com/mui-org/material-ui/blob/97d32b0ff3fae4537c20c79e619f132f4a5c5cbb/packages/mui-material/src/Chip/chipClasses.ts) for example – there are more than 20 classes that are incomplete if we count the permutation of elements (`root | avatar | icon | label | deleteIcon`), size (`small | medium | large`), and color (`primary | secondary | ...`).
This leads to a poor theming experience because developers need to know which specific key to customize.

We believe it would be better for developers if they could create custom styles by reading the component props, without ever needing to know what key they should use.

Fortunately, it is now possible in v5 because of the new style engine powered by emotion. Theming is simpler and more flexible. You only need to know the component's slot name and then provide an **object** (static overrides) or a **callback** (dynamic overrides).

## Using callback in `styleOverrides`

The callback gives you the `props` that the slot received. Most of the time you would use:

- `props.ownerState`: the combination of runtime props and internal states.
- `props.theme`: the theme object you provided to `ThemeProvider`, or the default one.

```jsx
import { ThemeProvider, createTheme } from '@mui/material/styles';

<ThemeProvider
theme={createTheme({
components: {
MuiChip: {
styleOverrides: {
// you can now use the theme without creating the initial theme!
root: ({ ownerState, theme }) => ({
padding: {
small: '8px 4px',
medium: '12px 6px',
large: '16px 8px',
}[ownerState.size],
...(ownerState.variant === 'outlined' && {
borderWidth: '2px',
...(ownerState.variant === 'primary' && {
borderColor: theme.palette.primary.light,
}),
}),
}),
label: {
padding: 0,
},
},
},
},
})}
>
...your app
</ThemeProvider>;
```

> 💡 The side benefit of using a callback is that you can use the runtime theme without creating the outer scoped variable.
### TypeScript

The callback is type-safe.

- `ownerState`: `ComponentProps` interface, eg. `ButtonProps`, `ChipProps`, etc.
- `theme`: `Theme` interface from `@mui/material/styles`.

```tsx
{
MuiChip: {
styleOverrides: {
// ownerState: ChipProps
// theme: Theme
root: ({ ownerState, theme }) => ({...}),
},
}
}
```

If you extend the interface via module augmentation like this:

```ts
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
dashed: true;
}
}
```

you will be able to see those props in `ownerState.variant` 🎉. `theme` can be augmented as well.

## Experimental `sx` function

Initially, `sx` was designed to be a prop that enables you to inject styles with a shorthand notation to components created with the `styled` API:

```jsx
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';

const Label = styled('span')({
fontWeight: 'bold',
fontSize: '0.875rem',
})

<Box sx={{ display: 'flex' }}>
<Label sx={{ color: 'text.secondary' }}>Label</Label>
</Box>;
```

> 💡 All MUI components are created with the `styled` API, so they accept `sx` prop by default.
`sx` helps developers write less code and be more productive once they are familiar with the API. With the callback support in `styleOverrides`, it is now possible to use an `sx`-like syntax in global theme overrides.

All you need is to use the [`experimental_sx`](/system/styled/#how-can-i-use-the-sx-syntax-with-the-styled-utility) function. In the following example, I use `sx` to theme the `Chip` component:

```jsx
import {
ThemeProvider,
createTheme,
experimental_sx as sx,
} from '@mui/material/styles';

<ThemeProvider
theme={createTheme({
components: {
MuiChip: {
styleOverrides: {
root: sx({
px: '12px', // shorthand for padding-left & right
py: '6px', // shorthand for padding-top & bottom
fontWeight: 500,
borderRadius: '8px',
}),
label: {
padding: 0,
},
},
},
},
})}
>
...your app
</ThemeProvider>;
```

If I want to add more styles based on these conditions:

- border color `palette.text.secondary` is applied when `<Chip variant="outlined" />`.
- font size is `0.875rem` in mobile viewport, `0.75rem` in larger than mobile viewport when `<Chip size="small" />`.

An array can be used as a return type to make the code easier to add/remove conditions:

```js
// The <ThemeProvider> is omitted for readability.
{
root: ({ ownerState }) => [
sx({
px: '12px',
py: '6px',
fontWeight: 500,
borderRadius: '8px',
}),
ownerState.variant === 'outlined' && ownerState.color === 'default' &&
sx({
borderColor: 'text.secondary',
}),
ownerState.size === 'small' &&
sx({
fontSize: { xs: '0.875rem', sm: '0.75rem' },
})
],
}
```

<hr />

**That's it for today!** Happy styling 💅.

I hope this small update makes your customization experience better than ever. Don't forget to share this update with your friends and colleagues.

To get more updates like this in the future, **subscribe to our newsletter** at the bottom of this page.

## Read more

- [Component theming](/customization/theme-components/)
- [All supported shorthands in `sx`](/system/the-sx-prop/#theme-aware-properties)
- [`sx` performance tradeoff](/system/basics/#performance-tradeoff)
- [`sx` with `styled`](/system/styled/#difference-with-the-sx-prop)
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ If you are not familiar `sx`, first check out [the concept](/system/the-sx-prop/

## Adding new component variants

> ⚠️ This API has been **deprecated** and will likely be removed in the next major release. If you want to apply styles based on props, take a look at [Overrides based on props](#overrides-based-on-props) instead.
>
> If you are interested to see the reasoning behind this change, check out [issue #30412](https://github.com/mui-org/material-ui/issues/30412)
You can use the `variants` key in the theme's `components` section to add new variants to MUI components. These new variants can specify what styles the component should have when specific props are applied.

The definitions are specified in an array, under the component's name. For each of them a CSS class is added to the HTML `<head>`. The order is important, so make sure that the styles that should win are specified last.
Expand Down
18 changes: 8 additions & 10 deletions docs/src/pages/guides/migration-v4/migration-v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ As the core components use emotion as their style engine, the props used by emot

- The props: `alignItems` `alignContent` and `justifyContent` and their `classes` and style overrides keys were removed: "align-items-xs-center", "align-items-xs-flex-start", "align-items-xs-flex-end", "align-items-xs-baseline", "align-content-xs-center", "align-content-xs-flex-start", "align-content-xs-flex-end", "align-content-xs-space-between", "align-content-xs-space-around", "justify-content-xs-center", "justify-content-xs-flex-end", "justify-content-xs-space-between", "justify-content-xs-space-around" and "justify-content-xs-space-evenly".
These props are now considered part of the system, not on the `Grid` component itself.
If you still wish to add overrides for them, you can use the `theme.components.MuiGrid.variants` options.
If you still wish to add overrides for them, you can use the [callback as a value in `styleOverrides`](/customization/theme-components/#overrides-based-on-props).

> ✅ This is handled in the [preset-safe codemod](#preset-safe).
Expand All @@ -1459,12 +1459,11 @@ As the core components use emotion as their style engine, the props used by emot
- marginTop: '20px',
- },
- },
+ variants: {
+ props: { alignItems: "flex-end" },
+ style: {
+ styleOverrides: ({ ownerState }) => ({
+ ...ownerState.alignItems === 'flex-end' && {
+ marginTop: '20px',
+ },
+ }],
+ }),
},
},
});
Expand Down Expand Up @@ -2364,7 +2363,7 @@ As the core components use emotion as their style engine, the props used by emot

- The following `classes` and style overrides keys were removed: "colorInherit", "colorPrimary", "colorSecondary", "colorTextPrimary", "colorTextSecondary", "colorError", "displayInline" and "displayBlock".
These props are now considered part of the system, not on the `Typography` component itself.
If you still wish to add overrides for them, you can use the `theme.components.MuiTypography.variants` options.
If you still wish to add overrides for them, you can use the [callback as a value in `styleOverrides`](/customization/theme-components/#overrides-based-on-props).
For example:

```diff
Expand All @@ -2376,12 +2375,11 @@ As the core components use emotion as their style engine, the props used by emot
- marginTop: '20px',
- },
- },
+ variants: [{
+ props: { color: 'secondary' },
+ style: {
+ styleOverrides: ({ ownerState }) => ({
+ ...ownerState.color === 'secondary' && {
+ marginTop: '20px',
+ },
+ }],
+ }),
},
},
});
Expand Down

0 comments on commit 29f02a0

Please sign in to comment.