diff --git a/benchmark/browser/README.md b/benchmark/browser/README.md
index 97b3e69eccdf33..9b73c532d8a380 100644
--- a/benchmark/browser/README.md
+++ b/benchmark/browser/README.md
@@ -16,163 +16,45 @@ You should use these numbers exclusively for comparing performance between diffe
yarn benchmark:browser
noop (baseline):
-
-20.10ms
-20.05ms
-19.28ms
-19.64ms
-20.96ms
-18.80ms
-18.39ms
-20.38ms
-18.85ms
-18.99ms
+mean: 4.70ms, median: 4.72ms
-------------
-Avg: 19.55ms
-Median: 19.46ms
-
-styled-components Box + @material-ui/system:
-
-184.17ms
-161.51ms
-168.84ms
-165.21ms
-163.43ms
-158.10ms
-158.81ms
-297.91ms
-161.25ms
-158.87ms
+React primitives:
+mean: 68.89ms, median: 64.02ms
-------------
-Avg: 177.81ms
-Median: 162.47ms
-
-styled-components Box + styled-system:
-
-142.66ms
-146.55ms
-141.12ms
-141.04ms
-139.45ms
-145.63ms
-141.36ms
-134.98ms
-123.98ms
-146.09ms
+React components:
+mean: 74.38ms, median: 74.46ms
-------------
-Avg: 140.29ms
-Median: 141.24ms
-
-Box emotion:
-
-143.21ms
-135.28ms
-122.53ms
-124.80ms
-143.69ms
-147.81ms
-138.16ms
-124.55ms
-140.32ms
-157.74ms
+Styled Material-UI:
+mean: 109.73ms, median: 109.46ms
-------------
-Avg: 137.81ms
-Median: 139.24ms
-
-Box @material-ui/styles:
-
-146.16ms
-131.37ms
-139.43ms
-158.55ms
-149.54ms
-131.81ms
-134.84ms
-151.08ms
-152.30ms
-130.69ms
+Styled emotion:
+mean: 102.59ms, median: 104.28ms
-------------
-Avg: 142.58ms
-Median: 142.79ms
-
-Box styled-components:
-
-145.59ms
-150.12ms
-179.04ms
-169.63ms
-148.21ms
-155.55ms
-182.55ms
-170.04ms
-153.14ms
-148.92ms
+Styled SC:
+mean: 104.06ms, median: 102.67ms
-------------
-Avg: 160.28ms
-Median: 154.35ms
-
-Basic styled-components box:
-
-141.73ms
-139.71ms
-121.01ms
-120.02ms
-121.81ms
-143.22ms
-135.67ms
-120.85ms
-121.08ms
-120.59ms
+makeStyles:
+mean: 93.81ms, median: 92.90ms
-------------
-Avg: 128.57ms
-Median: 121.44ms
-
-Chakra-UI box component:
-
-147.42ms
-128.51ms
-118.74ms
-110.01ms
-133.05ms
-130.20ms
-121.57ms
-119.11ms
-108.57ms
-134.90ms
+sx Material-UI box:
+mean: 187.98ms, median: 188.77ms
-------------
-Avg: 125.21ms
-Median: 125.04ms
-
-Theme-UI box sx prop:
-
-165.02ms
-141.07ms
-139.19ms
-185.45ms
-166.16ms
-138.83ms
-140.56ms
-139.02ms
-179.26ms
-165.58ms
+Box Material-UI:
+mean: 159.24ms, median: 157.90ms
-------------
-Avg: 156.01ms
-Median: 153.05ms
-
-Theme-UI div sx prop:
-
-131.07ms
-130.84ms
-130.99ms
-132.66ms
-132.24ms
-130.89ms
-131.11ms
-167.10ms
-154.42ms
-131.48ms
+sx Theme-UI box:
+mean: 164.22ms, median: 164.16ms
+-------------
+sx Theme-UI div:
+mean: 153.10ms, median: 152.77ms
+-------------
+Box Chakra-UI:
+mean: 154.95ms, median: 153.89ms
+-------------
+styled-components Box + @material-ui/system:
+mean: 176.82ms, median: 176.60ms
+-------------
+styled-components Box + styled-system:
+mean: 155.18ms, median: 154.63ms
-------------
-Avg: 137.28ms
-Median: 131.30ms
-Done in 31.83s.
```
diff --git a/benchmark/browser/index.js b/benchmark/browser/index.js
index 4bd1f0b727d96c..f997ff05f770e8 100644
--- a/benchmark/browser/index.js
+++ b/benchmark/browser/index.js
@@ -15,7 +15,7 @@ const Component = requirePerfScenarios(scenarioSuitePath).default;
const start = performance.now();
let end;
-function TestCase(props) {
+function Measure(props) {
const ref = React.useRef(null);
React.useLayoutEffect(() => {
@@ -28,20 +28,18 @@ function TestCase(props) {
};
});
- return (
-
- {props.children}
-
- );
+ return
{props.children}
;
}
-TestCase.propTypes = {
+Measure.propTypes = {
children: PropTypes.node,
};
ReactDOM.render(
-
-
- ,
+
+
+
+
+ ,
rootEl,
);
diff --git a/benchmark/browser/scenarios/basic-styled-components/index.js b/benchmark/browser/scenarios/basic-styled-components/index.js
deleted file mode 100644
index ec26cbce611919..00000000000000
--- a/benchmark/browser/scenarios/basic-styled-components/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as React from 'react';
-import { createMuiTheme } from '@material-ui/core/styles';
-import { spacing } from '@material-ui/system';
-import styledComponents, {
- ThemeProvider as StyledComponentsThemeProvider,
-} from 'styled-components';
-
-const materialSystemTheme = createMuiTheme();
-const BasicStyleComponents = styledComponents('div')(spacing);
-
-export default function BasicStyledComponents() {
- return (
-
- {new Array(1000).fill().map(() => (
-
- styled-components
-
- ))}
-
- );
-}
diff --git a/benchmark/browser/scenarios/box-chakra-ui/index.js b/benchmark/browser/scenarios/box-chakra-ui/index.js
index 7307bf072e00d5..8b29672f91a5e7 100644
--- a/benchmark/browser/scenarios/box-chakra-ui/index.js
+++ b/benchmark/browser/scenarios/box-chakra-ui/index.js
@@ -1,16 +1,11 @@
import * as React from 'react';
-import { Box, ThemeProvider, theme } from '@chakra-ui/core';
+import { Box, ThemeProvider } from '@chakra-ui/core';
-// Let's say you want to add custom colors
const customTheme = {
- ...theme,
colors: {
- ...theme.colors,
- brand: {
- 900: '#1a365d',
- 800: '#153e75',
- 700: '#2a69ac',
- },
+ text: '#000',
+ background: '#fff',
+ primary: '#33e',
},
};
@@ -19,13 +14,15 @@ export default function BoxChakraUi() {
{new Array(1000).fill().map(() => (
- chakra-ui
+ test case
))}
diff --git a/benchmark/browser/scenarios/box-emotion/index.js b/benchmark/browser/scenarios/box-emotion/index.js
deleted file mode 100644
index f7dd839c16ed4d..00000000000000
--- a/benchmark/browser/scenarios/box-emotion/index.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as React from 'react';
-import { createMuiTheme } from '@material-ui/core/styles';
-import styledEmotion from '@emotion/styled';
-import { ThemeProvider as EmotionTheme } from 'emotion-theming';
-import { styleFunction } from '@material-ui/core/Box';
-
-const materialSystemTheme = createMuiTheme();
-const Box = styledEmotion('div')(styleFunction);
-
-export default function BoxEmotion() {
- return (
-
- {new Array(1000).fill().map(() => (
-
- emotion
-
- ))}
-
- );
-}
diff --git a/benchmark/browser/scenarios/box-material-ui-styles/index.js b/benchmark/browser/scenarios/box-material-ui-styles/index.js
index 35ebad0df4b0b8..6eab23af21e65c 100644
--- a/benchmark/browser/scenarios/box-material-ui-styles/index.js
+++ b/benchmark/browser/scenarios/box-material-ui-styles/index.js
@@ -1,24 +1,21 @@
import * as React from 'react';
-import { createMuiTheme } from '@material-ui/core/styles';
-import { ThemeProvider as StylesThemeProvider } from '@material-ui/styles';
-import BoxStyles from '@material-ui/core/Box';
-
-const materialSystemTheme = createMuiTheme();
+import Box from '@material-ui/core/Box';
export default function BoxMaterialUIStyles() {
return (
-
+
{new Array(1000).fill().map(() => (
-
- @material-ui/styles
-
+ test case
+
))}
-
+
);
}
diff --git a/benchmark/browser/scenarios/box-styled-components/index.js b/benchmark/browser/scenarios/box-styled-components/index.js
deleted file mode 100644
index f9873fa8f6bcd4..00000000000000
--- a/benchmark/browser/scenarios/box-styled-components/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as React from 'react';
-import { createMuiTheme } from '@material-ui/core/styles';
-import { styleFunction } from '@material-ui/core/Box';
-import styledComponents, {
- ThemeProvider as StyledComponentsThemeProvider,
-} from 'styled-components';
-
-const materialSystemTheme = createMuiTheme();
-const BoxStyleComponents = styledComponents('div')(styleFunction);
-
-export default function BoxStyledComponents() {
- return (
-
- {new Array(1000).fill().map(() => (
-
- styled-components
-
- ))}
-
- );
-}
diff --git a/benchmark/browser/scenarios/components/index.js b/benchmark/browser/scenarios/components/index.js
new file mode 100644
index 00000000000000..887bfb0971a875
--- /dev/null
+++ b/benchmark/browser/scenarios/components/index.js
@@ -0,0 +1,15 @@
+import * as React from 'react';
+
+const Div = React.forwardRef(function Div(props, ref) {
+ return ;
+});
+
+export default function Components() {
+ return (
+
+ {new Array(1000).fill().map(() => (
+ test case
+ ))}
+
+ );
+}
diff --git a/benchmark/browser/scenarios/make-styles/index.js b/benchmark/browser/scenarios/make-styles/index.js
new file mode 100644
index 00000000000000..81173db4b3d970
--- /dev/null
+++ b/benchmark/browser/scenarios/make-styles/index.js
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ width: 200,
+ height: 200,
+ borderWidth: 3,
+ borderColor: 'white',
+ ':hover': {
+ backgroundColor: theme.palette.secondary.dark,
+ },
+ [theme.breakpoints.up('sm')]: {
+ backgroundColor: theme.palette.primary.main,
+ borderStyle: 'dashed',
+ },
+ },
+}));
+
+const Div = React.forwardRef(function Div(props, ref) {
+ const classes = useStyles();
+
+ return ;
+});
+
+export default function MakeStyles() {
+ return (
+
+ {new Array(1000).fill().map(() => (
+ test case
+ ))}
+
+ );
+}
diff --git a/benchmark/browser/scenarios/primitives/index.js b/benchmark/browser/scenarios/primitives/index.js
new file mode 100644
index 00000000000000..03fcff93b9274b
--- /dev/null
+++ b/benchmark/browser/scenarios/primitives/index.js
@@ -0,0 +1,11 @@
+import * as React from 'react';
+
+export default function Primitives() {
+ return (
+
+ {new Array(1000).fill().map(() => (
+ test case
+ ))}
+
+ );
+}
diff --git a/benchmark/browser/scenarios/styled-emotion/index.js b/benchmark/browser/scenarios/styled-emotion/index.js
new file mode 100644
index 00000000000000..0edabb3f598958
--- /dev/null
+++ b/benchmark/browser/scenarios/styled-emotion/index.js
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import { createMuiTheme } from '@material-ui/core/styles';
+import emotionStyled from '@emotion/styled';
+
+const Div = emotionStyled('div')(
+ ({ theme }) => `
+ width: 200px;
+ height: 200px;
+ border-width: 3px;
+ border-color: white;
+ :hover {
+ background-color: ${theme.palette.secondary.dark};
+ }
+ ${[theme.breakpoints.up('sm')]} {
+ background-color: ${theme.palette.primary.main};
+ border-style: 'dashed';
+ }
+`,
+);
+
+const theme = createMuiTheme();
+
+export default function StyledEmotion() {
+ return (
+
+ {new Array(1000).fill().map(() => (
+ test case
+ ))}
+
+ );
+}
diff --git a/benchmark/browser/scenarios/styled-material-ui/index.js b/benchmark/browser/scenarios/styled-material-ui/index.js
new file mode 100644
index 00000000000000..18849ead2d3861
--- /dev/null
+++ b/benchmark/browser/scenarios/styled-material-ui/index.js
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import { experimentalStyled as styled } from '@material-ui/core/styles';
+
+const Div = styled('div')(
+ ({ theme }) => `
+ width: 200px;
+ height: 200px;
+ border-width: 3px;
+ border-color: white;
+ :hover {
+ background-color: ${theme.palette.secondary.dark};
+ }
+ ${[theme.breakpoints.up('sm')]} {
+ background-color: ${theme.palette.primary.main};
+ border-style: 'dashed';
+ }
+`,
+);
+
+export default function StyledMaterialUI() {
+ return (
+
+ {new Array(1000).fill().map(() => (
+ test case
+ ))}
+
+ );
+}
diff --git a/benchmark/browser/scenarios/styled-sc/index.js b/benchmark/browser/scenarios/styled-sc/index.js
new file mode 100644
index 00000000000000..f81df8ad1317b5
--- /dev/null
+++ b/benchmark/browser/scenarios/styled-sc/index.js
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import { createMuiTheme } from '@material-ui/core/styles';
+import styledComponents, {
+ ThemeProvider as StyledComponentsThemeProvider,
+} from 'styled-components';
+
+const Div = styledComponents('div')(
+ ({ theme }) => `
+ width: 200px;
+ height: 200px;
+ border-width: 3px;
+ border-color: white;
+ :hover {
+ background-color: ${theme.palette.secondary.dark};
+ }
+ ${[theme.breakpoints.up('sm')]} {
+ background-color: ${theme.palette.primary.main};
+ border-style: 'dashed';
+ }
+`,
+);
+
+const theme = createMuiTheme();
+
+export default function StyledSC() {
+ return (
+
+ {new Array(1000).fill().map(() => (
+ test case
+ ))}
+
+ );
+}
diff --git a/benchmark/browser/scenarios/sx-prop-box-material-ui/index.js b/benchmark/browser/scenarios/sx-prop-box-material-ui/index.js
index 1988668b132ec3..aa5e393dc627ed 100644
--- a/benchmark/browser/scenarios/sx-prop-box-material-ui/index.js
+++ b/benchmark/browser/scenarios/sx-prop-box-material-ui/index.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
-export default function BoxSxPropMaterialUI() {
+export default function SxPropBoxMaterialUI() {
return (
{new Array(1000).fill().map(() => (
@@ -9,16 +9,16 @@ export default function BoxSxPropMaterialUI() {
sx={{
width: 200,
height: 200,
- backgroundColor: [undefined, 'primary.light', 'primary.main', 'primary.dark'],
borderWidth: '3px',
borderColor: 'white',
- borderStyle: [undefined, 'dashed', 'solid', 'dotted'],
+ backgroundColor: { sm: 'primary.main' },
+ borderStyle: { sm: 'dashed' },
':hover': {
backgroundColor: (theme) => theme.palette.secondary.dark,
},
}}
>
- material-ui
+ test case
))}
diff --git a/benchmark/browser/scenarios/sx-prop-box-theme-ui/index.js b/benchmark/browser/scenarios/sx-prop-box-theme-ui/index.js
index 588f1aa6fa0c36..95d329dc7668bb 100644
--- a/benchmark/browser/scenarios/sx-prop-box-theme-ui/index.js
+++ b/benchmark/browser/scenarios/sx-prop-box-theme-ui/index.js
@@ -32,7 +32,7 @@ export default function ThemeUISxPropBox() {
},
}}
>
- theme-ui
+ test case
))}
diff --git a/benchmark/browser/scenarios/sx-prop-div-theme-ui/index.js b/benchmark/browser/scenarios/sx-prop-div-theme-ui/index.js
index 38e9abb777c964..e9d33a4d2e8704 100644
--- a/benchmark/browser/scenarios/sx-prop-div-theme-ui/index.js
+++ b/benchmark/browser/scenarios/sx-prop-div-theme-ui/index.js
@@ -32,7 +32,7 @@ export default function ThemeUiSxProp() {
},
}}
>
- theme-ui
+ test case
))}
diff --git a/benchmark/browser/scripts/benchmark.js b/benchmark/browser/scripts/benchmark.js
index b4b9ed32bda7f5..6490072ee52bd6 100644
--- a/benchmark/browser/scripts/benchmark.js
+++ b/benchmark/browser/scripts/benchmark.js
@@ -56,22 +56,26 @@ const getMedian = (measures) => {
};
const printMeasure = (name, measures) => {
- console.log(`\n${name}:\n`);
+ console.log(`${name}:`);
let sum = 0;
const totalNum = measures.length;
measures.forEach((measure) => {
sum += measure;
- console.log(`${measure.toFixed(2)}ms`);
+ // Uncomment for more details
+ // console.log(`${measure.toFixed(2)}ms`);
});
+ console.log(
+ `mean: ${Number(sum / totalNum).toFixed(2)}ms, median: ${Number(getMedian(measures)).toFixed(
+ 2,
+ )}ms`,
+ );
console.log('-------------');
- console.log(`Avg: ${Number(sum / totalNum).toFixed(2)}ms`);
- console.log(`Median: ${Number(getMedian(measures)).toFixed(2)}ms`);
};
-async function runMeasures(browser, testCaseName, testCase, times) {
+async function runMeasures(browser, testCaseName, testCase, times = 10) {
const measures = [];
for (let i = 0; i < times; i += 1) {
@@ -95,32 +99,36 @@ async function run() {
const [server, browser] = await Promise.all([createServer({ port: PORT }), createBrowser()]);
try {
- await runMeasures(browser, 'noop (baseline)', './noop/index.js', 10);
+ // Test that there no significant offset
+ await runMeasures(browser, 'noop (baseline)', './noop/index.js');
+ // Test the cost of React primitives
+ await runMeasures(browser, 'React primitives', './primitives/index.js');
+ // Test the cost of React components abstraction
+ await runMeasures(browser, 'React components', './components/index.js');
+ // Test that @material-ui/styled-engine doesn't add an signifiant overhead
+ await runMeasures(browser, 'Styled Material-UI', './styled-material-ui/index.js');
+ await runMeasures(browser, 'Styled emotion', './styled-emotion/index.js');
+ await runMeasures(browser, 'Styled SC', './styled-sc/index.js');
+ // Test the performance compared to the v4 standard
+ await runMeasures(browser, 'makeStyles', './make-styles/index.js');
+ // Test that the sx prop vs props spreaing has no signficiant difference
+ await runMeasures(browser, 'sx Material-UI box', './sx-prop-box-material-ui/index.js');
+ await runMeasures(browser, 'Box Material-UI', './box-material-ui-styles/index.js');
+ // Test the Box perf with alternatives
+ await runMeasures(browser, 'sx Theme-UI box', './sx-prop-box-theme-ui/index.js');
+ await runMeasures(browser, 'sx Theme-UI div', './sx-prop-div-theme-ui/index.js');
+ await runMeasures(browser, 'Box Chakra-UI', './box-chakra-ui/index.js');
+ // Test the system perf difference with alternatives
await runMeasures(
browser,
'styled-components Box + @material-ui/system',
'./styled-components-box-material-ui-system/index.js',
- 10,
);
await runMeasures(
browser,
'styled-components Box + styled-system',
'./styled-components-box-styled-system/index.js',
- 10,
);
- await runMeasures(browser, 'Box emotion', './box-emotion/index.js', 10);
- await runMeasures(browser, 'Box @material-ui/styles', './box-material-ui-styles/index.js', 10);
- await runMeasures(browser, 'Box styled-components', './box-styled-components/index.js', 10);
- await runMeasures(
- browser,
- 'Basic styled-components box',
- './basic-styled-components/index.js',
- 10,
- );
- await runMeasures(browser, 'Chakra-UI box component', './box-chakra-ui/index.js', 10);
- await runMeasures(browser, 'Theme-UI box sx prop', './sx-prop-box-theme-ui/index.js', 10);
- await runMeasures(browser, 'Theme-UI div sx prop', './sx-prop-div-theme-ui/index.js', 10);
- await runMeasures(browser, 'Material-UI box sx prop', './sx-prop-box-material-ui/index.js', 10);
} finally {
await Promise.all([browser.close(), server.close()]);
}
diff --git a/docs/src/pages/system/basics/basics.md b/docs/src/pages/system/basics/basics.md
index 7c21300d257b82..6c3de1f29ce87a 100644
--- a/docs/src/pages/system/basics/basics.md
+++ b/docs/src/pages/system/basics/basics.md
@@ -142,9 +142,34 @@ This prop provides a superset of CSS that maps values directly from the theme, d
### When to use it?
-The styled-components's API is great to build low-level components that need to support a wide variety of contexts.
+- **styled-components**: the API is great to build components that need to support a wide variety of contexts. These components are used in many different parts of the application and support different combinations of props.
+- **`sx` prop**: the API is great to apply one-off styles. It's called "utility" for this reason.
-The `sx` prop is great anytime one-off styles need to be applied. It's called "utility" for this reason.
+### Performance tradeoff
+
+The system relies on CSS-in-JS. It works with both emotion and styled-components.
+
+Pros:
+
+- 📚 It allows a lot of flexibility in the API. The `sx` prop supports a superset of CSS. There is **no need to learn CSS twice**. You are set once you have learn the standardized CSS syntax, it's safe, it hasn't changed for a decade. Then, you can **optionally** learn the shorthands if you value the save of time they bring.
+- 📦 No need for purge. Only the used CSS on the page is included. The initial bundle size cost is **fixed**. It's not growing with the number of used CSS properties.
+ You pay the cost of [@emotion/react](https://bundlephobia.com/result?p=@emotion/react) and [@material-ui/system](https://bundlephobia.com/result?p=@material-ui/system). It cost around ~15 kB gzipped.
+ If you are already using the core components, it comes with no extra overhead.
+
+Cons:
+
+- The runtime performance take a hit.
+
+ | Benchmark case | Code snippet | Time normalized |
+ | :-------------------------------- | :------------------- | --------------- |
+ | a. Render 1,000 primitives | `` | 100ms |
+ | b. Render 1,000 components | `
` | 110ms |
+ | c. Render 1,000 styled components | `
` | 160ms |
+ | d. Render 1,000 Box | `` | 270ms |
+
+ _Head to the [benchmark folder](https://github.com/mui-org/material-ui/tree/next/benchmark/browser) for a reproduction of these metrics._
+
+ We believe that for most applications, it's **fast enough**. There are simple workarounds when performance becomes critical. For instance, when rendering a list with many items, you can use a CSS child selector to have a single "style injection" point (using d. for the wrapper and a. for each item).
## Usage
@@ -315,6 +340,7 @@ All core Material-UI components will support the `sx` prop.
### 2. Box
[`Box`](/components/box/) is a lightweight component that gives access to the `sx` prop, and can be used as a utility component, and as a wrapper for other components.
+It renders a `` element by default.
### 3. Custom components