Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[blog] Introducing callback support in style overrides #30668

Merged
merged 36 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6fe0ce6
add blog page
siriwatknp Jan 17, 2022
9d91130
add tag
siriwatknp Jan 17, 2022
6b73958
add description
siriwatknp Jan 17, 2022
085fd64
fix grammar
siriwatknp Jan 17, 2022
c41356a
yarn prettier
siriwatknp Jan 17, 2022
9d8e597
Merge branch 'master' of https://github.com/mui-org/material-ui into …
siriwatknp Jan 18, 2022
e97de4f
fix content
siriwatknp Jan 18, 2022
2692997
enable blog index
siriwatknp Jan 18, 2022
b7aec93
misc fix
siriwatknp Jan 18, 2022
085235c
use px
siriwatknp Jan 18, 2022
eceb9d0
Update docs/pages/blog/mui-material-v5-3.md
siriwatknp Jan 19, 2022
6664c86
Apply suggestions from code review
siriwatknp Jan 20, 2022
ca5de6c
run prettier
siriwatknp Jan 20, 2022
5991bac
update timestamp
siriwatknp Jan 20, 2022
154c63a
fix display tags logic
siriwatknp Jan 20, 2022
46e6c02
add variant theming deprecated comment
siriwatknp Jan 20, 2022
a11c5b9
replace variants with styleOverrides in migration docs
siriwatknp Jan 20, 2022
b322eee
Merge branch 'master' of https://github.com/mui-org/material-ui into …
siriwatknp Jan 20, 2022
c90ecf5
add runtime error message
siriwatknp Jan 20, 2022
36e6471
remove test for now
siriwatknp Jan 20, 2022
638c295
run extract error codes
siriwatknp Jan 20, 2022
bac3848
run prettier
siriwatknp Jan 20, 2022
cbeaae1
Apply suggestions from code review
siriwatknp Jan 20, 2022
f541dd0
add prettier to yarn error-code
siriwatknp Jan 20, 2022
22b36af
rename the blog post
siriwatknp Jan 20, 2022
682558d
Merge branch 'blog/v5.3' of github.com:siriwatknp/material-ui into bl…
siriwatknp Jan 20, 2022
529a2a7
remove target blank
siriwatknp Jan 20, 2022
3918ec6
revert changes
siriwatknp Jan 20, 2022
12787ed
rename blog post file
siriwatknp Jan 21, 2022
756da13
Apply suggestions from code review
siriwatknp Jan 21, 2022
56e8a13
remove deprecation
siriwatknp Jan 25, 2022
470cc58
remove variant mention from the post
siriwatknp Jan 25, 2022
9746286
Merge branch 'master' into blog/v5.3
siriwatknp Jan 25, 2022
358aa14
Apply suggestions from code review
siriwatknp Jan 26, 2022
979e77b
add example description
siriwatknp Jan 26, 2022
2bb4d30
Apply suggestions from code review
siriwatknp Jan 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/pages/blog/mui-material-v5-3.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 './mui-material-v5-3.md?@mui/markdown';

export default function Page() {
return <TopLayoutBlog docs={docs} />;
}
238 changes: 238 additions & 0 deletions docs/pages/blog/mui-material-v5-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
---
title: Material v5.3 — Theming upgrade
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
description: We are excited to announce the callback support for global theme overrides!
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
date: 2022-01-18T00:00:00.000Z
authors: ['siriwatknp']
tags: ['MUI Core']
danilo-leal marked this conversation as resolved.
Show resolved Hide resolved
card: false
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
---

## TL;DR

- `styleOverrides` supports callback as a value that allows dynamic style based on props:

```jsx
<ThemeProvider
theme={createTheme({
components: {
MuiButton: {
styleOverrides: {
// ownerState = public props + component's internal state
root: ({ ownerState, theme }) => ({
...(ownerState.variant === outlined && {
borderWidth: '2px',
borderRadius: theme.shape.borderRadius,
}),
}),
},
},
},
})}
/>
```

- The callback also works with the [experimental `sx`](/system/styled/#how-can-i-use-the-sx-syntax-with-the-styled-utility) function:

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

<ThemeProvider
theme={createTheme({
components: {
MuiButton: {
styleOverrides: {
startIcon: sx({
// shorthand for margin-right
mr: { xs: '4px', sm: '6px', md: '8px' },
}),
endIcon: sx({
// shorthand for margin-left
ml: { xs: '4px', sm: '6px', md: '8px' },
}),
},
},
},
})}
/>;
```

- [Variant theming](/customization/theme-components/#adding-new-component-variants) is deprecated. (We will provide a codemod for migrating it).

## The problems

In v4, the style engine library was JSS which has some limitations. Style overrides were not able to support dynamic props via a callback so we relied on using classes. Take a look at [`Chip` classes](https://github.com/mui-org/material-ui/blob/master/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 bad theming experience because developers need to know the specific key in order to customize.
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

It would be better if developers can read the props and create a custom style without knowing what key they should use.
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

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 object you provide 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
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

The callback is typed-safe.
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

- `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` is designed to be a prop that you can inject style with shorthand notation to the component created by `styled` API:
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

```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 by `styled` API, so they accept `sx` prop by default.
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

`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 `sx` like syntax at the global theme overrides.

All you need to do is using the [`experimental_sx`](/system/styled/#how-can-i-use-the-sx-syntax-with-the-styled-utility) function:
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

```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 you want to create a dynamic style based on props, you can return an array from the callback:

```js
{
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' },
})
],
// ...other keys
}
```

<hr />

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

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

To get more updates in the future, **subscribe to our newsletter** at the bottom of this page.
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

## Read more

siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
- [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)
6 changes: 4 additions & 2 deletions docs/src/components/home/StartToday.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ export default function StartToday() {
<Paper
component={Link}
href={ROUTES.blog}
target="_blank"
rel="noreferrer noopener"
{...(ROUTES.blog.startsWith('http') && {
target: '_blank',
rel: 'noreferrer noopener',
})}
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
noLinkStyle
variant="outlined"
sx={{ p: 2, height: '100%' }}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/featureToggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
module.exports = {
nav_products: true,
enable_website_banner: false,
enable_blog_index: process.env.NODE_ENV !== 'production' || process.env.PULL_REQUEST,
enable_blog_index: true,
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
// TODO: cleanup once migration is done
enable_product_scope: false, // related to new structure change
enable_redirects: false, // related to new structure change
Expand Down
4 changes: 3 additions & 1 deletion docs/src/modules/components/TopLayoutBlog.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import HeroEnd from 'docs/src/components/home/HeroEnd';
import { useRouter } from 'next/router';
import { exactProp } from '@mui/utils';
import Divider from '@mui/material/Divider';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import Avatar from '@mui/material/Avatar';
import MarkdownElement from 'docs/src/modules/components/MarkdownElement';
import ROUTES from 'docs/src/route';
import ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';
import Link from 'docs/src/modules/components/Link';

export const authors = {
oliviertassinari: {
Expand Down Expand Up @@ -199,6 +199,8 @@ function TopLayoutBlog(props) {
</Typography>
<Link
href={`https://github.com/${authors[author].github}`}
target="_blank"
rel="noreferrer noopener"
color="text.secondary"
variant="body2"
sx={{ fontWeight: 500 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const finalTheme = createTheme({

{{"demo": "pages/customization/theme-components/GlobalThemeOverrideCallback.js"}}

> ⚠️ the callback return type should be an object or an array, we don't support template literals(` `` `) as return type.

### Using `sx` (experimental) syntax

If you are not familiar `sx`, first check out [the concept](/system/the-sx-prop) and [the difference with the `styled`](/system/styled/#difference-with-the-sx-prop).
Expand Down