Repository: mui/material-ui (TypeScript)
Date: 2021-06-14 06:25:19+00:00
Task type: Feature

Issue:
[theme] Improve the breakpoints values
According to the official https://material.io/design/layout/responsive-layout-grid.html#breakpoints, the breakpoints should be as follow:

xs: 0 - 600 
sm: 600 - 1024
md: 1024 - 1440
lg: 1440 - 1920
xl: > 1920

Yet currently, MUI has the following as default
xs: 0 - 600 
**sm: 600 - 960
md: 960 - 1280
lg: 1280 - 1920**
xl: > 1920

---

Edit:

- The values were updated in https://material.io/blog/material-design-for-large-screens, the new values: https://material.io/design/layout/responsive-layout-grid.html#breakpoints


Issue discussion:
@matthewkwong2 Thanks for raising this issue, I have been keen to look into how we can improve the breakpoint values to better match the requirements of today's devices. IMHO we should partially ignore the recommendation of Material Design and look at what most teams use in their application/website, it will allow us to pick better values (crowd wisdom).

### Design system benchmark

- Material-UI
  - xs: 0
  - sm: 600
  - md: 960
  - lg: 1280
  - xl: 1920
- [Bootstrap](https://deploy-preview-31349--twbs-bootstrap.netlify.app/docs/5.0/layout/breakpoints/)
  - xs: 0
  - sm: 576
  - md: 768 
  - lg: 992
  - xl: 1200
  - xxl: 1400
- [TailwindCSS](https://tailwindcss.com/docs/breakpoints)
  - xs: 0
  - sm: 640
  - md: 768 
  - lg: 1024
  - xl: 1280
  - 2xl: 1536
- [StackOverflow](https://stackoverflow.design/product/guidelines/conditional-classes/#responsive)
  - xs: 0
  - sm: 640
  - md: 980 
  - lg: 1264
- [GitHub](https://github.com/primer/components/blob/master/src/theme-preval.js#L94)
  - xs: 0
  - sm: 544
  - md: 768 
  - lg: 1012
  - xlg: 1280
- [Chakra](https://github.com/chakra-ui/chakra-ui/blob/d1c8f1ada108495c56e50766730352a6cfb642e7/packages/theme/src/foundations/breakpoints.ts)
  - xs: 0
  - sm: 480
  - md: 768 
  - lg: 992
  - xl: 1280
 

### Device resolution benchmark

I figure the easiest would be to look at what Framer have, as I imagine they have spent quite some time thinking about what screen to optimize the design for:

<img width="229" alt="Capture d’écran 2020-07-24 à 13 32 36" src="https://user-images.githubusercontent.com/3165635/88387160-35e08200-cdb2-11ea-9689-6c8973bc667a.png">
<img width="228" alt="Capture d’écran 2020-07-24 à 13 32 45" src="https://user-images.githubusercontent.com/3165635/88387164-37aa4580-cdb2-11ea-911a-b84bf33d4f5c.png">
<img width="227" alt="Capture d’écran 2020-07-24 à 13 32 51" src="https://user-images.githubusercontent.com/3165635/88387166-3842dc00-cdb2-11ea-92c1-c9339fc31c37.png">


### First proposal

I think that we should aim for matching regular devices sizes:

  - xs: **0**. match-all, especially phones
  - sm: **600**, most phones seem to be below this value. We start to match tablets.
  - md: **900** most tablets seem to be below this value. We start to match small laptops. 
  - lg: **1200** most laptops seem to be below this value: We start to match large screens. I have a 13,3" MacBook Pro 1280px.
  - xl: **1500** We start to match extra-large screens. A 21" screen is 1680px, a 24" screen is 1920px. It rarely makes sense to have a layout wider than this. It would match the second medium window range of MD.

Note that the proposed breakpoints are designed to be divisible by 8 with a 0 rest (default grid is 4px) and to be divisible by 12 for the grid to have absolute values.

```js
  breakpoints: {
    values: {
      xs: 0, // phone
      sm: 600, // tablets
      md: 900, // small laptop
      lg: 1200, // desktop
      xl: 1500, // large screens
    },
  },
```

---

More broadly, It would be great to hear change proposals from the community, especially around the **why**.
It might also be a good idea to take orientations into consideration. One example will be dynamically loading fullscreen backgrounds.
How would you take the orientation into account? 
Good afternoon.

Any progress on this issue?  Has the team decided which breakpoints to support?

Thanks!

@chrisVillanueva So far, we are going with "First proposal".
I propose the second option. The main reason is to not introduce high impact because it is a breaking change (if we do it for v5).

I assume that most of the project implement responsive from xs-lg. I propose that the change only affect lg-xl like this
xs: 0 - 600 (same as v4)
sm: 600 - 960 (same as v4)
md: 960 - 1280 (same as v4)
lg: 1280 - 1536 (changed)
xl: > 1536 (changed)

This should affect only projects that support extra large screen.

> 1536 is divided by 12 and tailwindcss is using it.

@siriwatknp Thoughts:

- I think that going from 1920 to a lower value is a definitive win 👍 
- Material Design has recently updated its guidelines for breakpoints. They simplified them: https://material.io/design/layout/responsive-layout-grid.html#breakpoints.

  - xs: 0 // phones
  - sm: 600 // tablets 1
  - md: 905 // tablets 2
  - lg: 1240 // laptops
  - xl: 1440 // desktops

- we have been moving toward considering the breakpoints as a value, and never as a range (BC already done in v5).
- We have implemented [the rebranding](https://next.material-ui.com/branding/pricing/) with the value of the [first proposal](https://github.com/mui-org/material-ui/issues/21902#issuecomment-663488866) as a mean to stress test them.

https://github.com/mui-org/material-ui/blob/9acf8be6d2fd08210665c294830df3c85c014214/docs/src/modules/branding/BrandingRoot.tsx#L104-L112

Solution patch:
diff --git a/docs/src/modules/branding/BrandingRoot.tsx b/docs/src/modules/branding/BrandingRoot.tsx
--- a/docs/src/modules/branding/BrandingRoot.tsx
+++ b/docs/src/modules/branding/BrandingRoot.tsx
@@ -101,15 +101,6 @@ let theme = createTheme({
       '"Segoe UI Symbol"',
     ].join(','),
   },
-  breakpoints: {
-    values: {
-      xs: 0, // phones
-      sm: 600, // tablets
-      md: 900, // small laptops
-      lg: 1200, // desktops
-      xl: 1500, // large screens
-    },
-  },
 });
 
 function getButtonColor(color: string) {
diff --git a/docs/src/pages/customization/breakpoints/breakpoints.md b/docs/src/pages/customization/breakpoints/breakpoints.md
--- a/docs/src/pages/customization/breakpoints/breakpoints.md
+++ b/docs/src/pages/customization/breakpoints/breakpoints.md
@@ -15,9 +15,9 @@ Each breakpoint (a key) matches with a _fixed_ screen width (a value):
 
 - **xs,** extra-small: 0px
 - **sm,** small: 600px
-- **md,** medium: 960px
-- **lg,** large: 1280px
-- **xl,** extra-large: 1920px
+- **md,** medium: 900px
+- **lg,** large: 1200px
+- **xl,** extra-large: 1536px
 
 These values can be [customized](#custom-breakpoints).
 
@@ -77,9 +77,9 @@ const theme = createTheme({
     values: {
       xs: 0,
       sm: 600,
-      md: 960,
-      lg: 1280,
-      xl: 1920,
+      md: 900,
+      lg: 1200,
+      xl: 1536,
     },
   },
 });
@@ -94,7 +94,7 @@ const theme = createTheme({
       mobile: 0,
       tablet: 640,
       laptop: 1024,
-      desktop: 1280,
+      desktop: 1200,
     },
   },
 });
@@ -139,7 +139,7 @@ const styles = (theme) => ({
   root: {
     backgroundColor: 'blue',
     // Match [md, ∞)
-    //       [960px, ∞)
+    //       [900px, ∞)
     [theme.breakpoints.up('md')]: {
       backgroundColor: 'red',
     },
@@ -164,7 +164,7 @@ const styles = (theme) => ({
   root: {
     backgroundColor: 'blue',
     // Match [0, md)
-    //       [0, 960px)
+    //       [0, 900px)
     [theme.breakpoints.down('md')]: {
       backgroundColor: 'red',
     },
@@ -190,7 +190,7 @@ const styles = (theme) => ({
     backgroundColor: 'blue',
     // Match [md, md + 1)
     //       [md, lg)
-    //       [960px, 1280px)
+    //       [900px, 1200px)
     [theme.breakpoints.only('md')]: {
       backgroundColor: 'red',
     },
@@ -216,7 +216,7 @@ const styles = (theme) => ({
   root: {
     backgroundColor: 'blue',
     // Match [sm, md)
-    //       [600px, 960px)
+    //       [600px, 900px)
     [theme.breakpoints.between('sm', 'md')]: {
       backgroundColor: 'red',
     },
diff --git a/docs/src/pages/guides/migration-v4/migration-v4.md b/docs/src/pages/guides/migration-v4/migration-v4.md
--- a/docs/src/pages/guides/migration-v4/migration-v4.md
+++ b/docs/src/pages/guides/migration-v4/migration-v4.md
@@ -189,6 +189,39 @@ export default function PlainCssPriority() {
   }
   ```
 
+- The default breakpoints were changed to better match the common use cases. They also better match the Material Design guidelines. [Read more about the change](https://github.com/mui-org/material-ui/issues/21902)
+
+  ```diff
+  {
+    xs: 0,
+    sm: 600,
+  - md: 960,
+  + md: 900,
+  - lg: 1280,
+  + lg: 1200,
+  - xl: 1920,
+  + xl: 1536,
+  }
+  ```
+
+  If you prefer the old breakpoint values, use the snippet below.
+
+  ```js
+  import { createTheme } from '@material-ui/core/styles';
+
+  const theme = createTheme({
+    breakpoints: {
+      values: {
+        xs: 0,
+        sm: 600,
+        md: 960,
+        lg: 1280,
+        xl: 1920,
+      },
+    },
+  });
+  ```
+
 #### Upgrade helper
 
 For a smoother transition, the `adaptV4Theme` helper allows you to iteratively upgrade some of the theme changes to the new theme structure.
diff --git a/packages/material-ui-system/src/breakpoints.js b/packages/material-ui-system/src/breakpoints.js
--- a/packages/material-ui-system/src/breakpoints.js
+++ b/packages/material-ui-system/src/breakpoints.js
@@ -5,11 +5,11 @@ import merge from './merge';
 // The breakpoint **start** at this value.
 // For instance with the first breakpoint xs: [xs, sm[.
 const values = {
-  xs: 0,
-  sm: 600,
-  md: 960,
-  lg: 1280,
-  xl: 1920,
+  xs: 0, // phone
+  sm: 600, // tablets
+  md: 900, // small laptop
+  lg: 1200, // desktop
+  xl: 1536, // large screens
 };
 
 const defaultBreakpoints = {
diff --git a/packages/material-ui-system/src/createTheme/createBreakpoints.js b/packages/material-ui-system/src/createTheme/createBreakpoints.js
--- a/packages/material-ui-system/src/createTheme/createBreakpoints.js
+++ b/packages/material-ui-system/src/createTheme/createBreakpoints.js
@@ -8,11 +8,11 @@ export default function createBreakpoints(breakpoints) {
     // The breakpoint **start** at this value.
     // For instance with the first breakpoint xs: [xs, sm).
     values = {
-      xs: 0,
-      sm: 600,
-      md: 960,
-      lg: 1280,
-      xl: 1920,
+      xs: 0, // phone
+      sm: 600, // tablets
+      md: 900, // small laptop
+      lg: 1200, // desktop
+      xl: 1536, // large screens
     },
     unit = 'px',
     step = 5,
diff --git a/packages/material-ui/src/styles/cssUtils.js b/packages/material-ui/src/styles/cssUtils.js
--- a/packages/material-ui/src/styles/cssUtils.js
+++ b/packages/material-ui/src/styles/cssUtils.js
@@ -102,7 +102,7 @@ export function responsiveProperty({
   min,
   max,
   unit = 'rem',
-  breakpoints = [600, 960, 1280],
+  breakpoints = [600, 900, 1200],
   transform = null,
 }) {
   const output = {


Test patch:
diff --git a/packages/material-ui-system/src/createTheme/createBreakpoints.test.js b/packages/material-ui-system/src/createTheme/createBreakpoints.test.js
--- a/packages/material-ui-system/src/createTheme/createBreakpoints.test.js
+++ b/packages/material-ui-system/src/createTheme/createBreakpoints.test.js
@@ -18,7 +18,7 @@ describe('createBreakpoints', () => {
     });
 
     it('should work for md', () => {
-      expect(breakpoints.up('md')).to.equal('@media (min-width:960px)');
+      expect(breakpoints.up('md')).to.equal('@media (min-width:900px)');
     });
 
     it('should work for custom breakpoints', () => {
@@ -32,7 +32,7 @@ describe('createBreakpoints', () => {
     });
 
     it('should work for md', () => {
-      expect(breakpoints.down('md')).to.equal('@media (max-width:959.95px)');
+      expect(breakpoints.down('md')).to.equal('@media (max-width:899.95px)');
     });
 
     it('should work for xs', () => {
@@ -44,7 +44,7 @@ describe('createBreakpoints', () => {
     });
 
     it('should work for xl', () => {
-      expect(breakpoints.down('xl')).to.equal('@media (max-width:1919.95px)');
+      expect(breakpoints.down('xl')).to.equal('@media (max-width:1535.95px)');
     });
 
     it('should work for custom breakpoints', () => {
@@ -59,7 +59,7 @@ describe('createBreakpoints', () => {
   describe('between', () => {
     it('should work', () => {
       expect(breakpoints.between('sm', 'md')).to.equal(
-        '@media (min-width:600px) and (max-width:959.95px)',
+        '@media (min-width:600px) and (max-width:899.95px)',
       );
     });
 
@@ -71,7 +71,7 @@ describe('createBreakpoints', () => {
 
     it('should work on largest breakpoints', () => {
       expect(breakpoints.between('lg', 'xl')).to.equal(
-        '@media (min-width:1280px) and (max-width:1919.95px)',
+        '@media (min-width:1200px) and (max-width:1535.95px)',
       );
     });
 
@@ -84,11 +84,11 @@ describe('createBreakpoints', () => {
 
   describe('only', () => {
     it('should work', () => {
-      expect(breakpoints.only('md')).to.equal('@media (min-width:960px) and (max-width:1279.95px)');
+      expect(breakpoints.only('md')).to.equal('@media (min-width:900px) and (max-width:1199.95px)');
     });
 
     it('on xl should call up', () => {
-      expect(breakpoints.only('xl')).to.equal('@media (min-width:1920px)');
+      expect(breakpoints.only('xl')).to.equal('@media (min-width:1536px)');
     });
 
     it('should work for custom breakpoints', () => {
diff --git a/packages/material-ui/src/Grid/Grid.test.js b/packages/material-ui/src/Grid/Grid.test.js
--- a/packages/material-ui/src/Grid/Grid.test.js
+++ b/packages/material-ui/src/Grid/Grid.test.js
@@ -2,6 +2,7 @@ import * as React from 'react';
 import { expect } from 'chai';
 import { createMount, describeConformanceV5, createClientRender, screen } from 'test/utils';
 import { createTheme, ThemeProvider } from '@material-ui/core/styles';
+import defaultTheme from '@material-ui/core/styles/defaultTheme';
 import Grid, { gridClasses as classes } from '@material-ui/core/Grid';
 import { generateRowGap, generateColumnGap } from './Grid';
 
@@ -90,7 +91,7 @@ describe('<Grid />', () => {
         marginTop: '-8px',
         width: 'calc(100% + 8px)',
       },
-      '@media (min-width:600px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
         '& > .MuiGrid-item': {
           paddingTop: '16px',
         },
@@ -115,7 +116,7 @@ describe('<Grid />', () => {
         marginLeft: '-8px',
         width: 'calc(100% + 8px)',
       },
-      '@media (min-width:600px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
         '& > .MuiGrid-item': {
           paddingLeft: '16px',
         },
diff --git a/packages/material-ui/src/Stack/Stack.test.js b/packages/material-ui/src/Stack/Stack.test.js
--- a/packages/material-ui/src/Stack/Stack.test.js
+++ b/packages/material-ui/src/Stack/Stack.test.js
@@ -3,6 +3,7 @@ import { expect } from 'chai';
 import { createMount, createClientRender, describeConformanceV5 } from 'test/utils';
 import Stack from '@material-ui/core/Stack';
 import { createTheme } from '@material-ui/core/styles';
+import defaultTheme from '@material-ui/core/styles/defaultTheme';
 import { style } from './Stack';
 
 describe('<Stack />', () => {
@@ -37,14 +38,14 @@ describe('<Stack />', () => {
         },
         flexDirection: 'column',
       },
-      '@media (min-width:600px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginLeft: '16px',
         },
         flexDirection: 'row',
       },
-      '@media (min-width:960px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginLeft: '32px',
@@ -64,14 +65,14 @@ describe('<Stack />', () => {
         theme,
       }),
     ).to.deep.equal({
-      '@media (min-width:600px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '16px',
         },
         flexDirection: 'column',
       },
-      '@media (min-width:960px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginLeft: '16px',
@@ -92,13 +93,13 @@ describe('<Stack />', () => {
         theme,
       }),
     ).to.deep.equal({
-      '@media (min-width:600px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '16px',
         },
       },
-      '@media (min-width:960px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '32px',
@@ -119,19 +120,19 @@ describe('<Stack />', () => {
         theme,
       }),
     ).to.deep.equal({
-      '@media (min-width:600px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '16px',
         },
       },
-      '@media (min-width:960px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '0px',
         },
       },
-      '@media (min-width:1280px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.lg}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '32px',
@@ -178,7 +179,7 @@ describe('<Stack />', () => {
         },
         flexDirection: 'column',
       },
-      '@media (min-width:1280px)': {
+      [`@media (min-width:${defaultTheme.breakpoints.values.lg}px)`]: {
         '& > :not(style) + :not(style)': {
           margin: 0,
           marginTop: '16px',
diff --git a/packages/material-ui/src/styles/adaptV4Theme.test.js b/packages/material-ui/src/styles/adaptV4Theme.test.js
--- a/packages/material-ui/src/styles/adaptV4Theme.test.js
+++ b/packages/material-ui/src/styles/adaptV4Theme.test.js
@@ -1,4 +1,5 @@
 import { expect } from 'chai';
+import defaultTheme from '@material-ui/core/styles/defaultTheme';
 import adaptV4Theme from './adaptV4Theme';
 
 describe('adaptV4Theme', () => {
@@ -208,7 +209,7 @@ describe('adaptV4Theme', () => {
       expect(transformedTheme.mixins.gutters()).to.deep.equal({
         paddingLeft: defaultSpacing * 2,
         paddingRight: defaultSpacing * 2,
-        '@media (min-width:600px)': {
+        [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
           paddingLeft: defaultSpacing * 3,
           paddingRight: defaultSpacing * 3,
         },
@@ -228,7 +229,7 @@ describe('adaptV4Theme', () => {
       expect(transformedTheme.mixins.gutters()).to.deep.equal({
         paddingLeft: spacing * 2,
         paddingRight: spacing * 2,
-        '@media (min-width:600px)': {
+        [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
           paddingLeft: spacing * 3,
           paddingRight: spacing * 3,
         },
@@ -256,7 +257,7 @@ describe('adaptV4Theme', () => {
       expect(transformedTheme.mixins.gutters()).to.deep.equal({
         paddingLeft: defaultSpacing * 2,
         paddingRight: defaultSpacing * 2,
-        '@media (min-width:600px)': {
+        [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: {
           paddingLeft: defaultSpacing * 3,
           paddingRight: defaultSpacing * 3,
         },
diff --git a/packages/material-ui/src/styles/responsiveFontSizes.test.js b/packages/material-ui/src/styles/responsiveFontSizes.test.js
--- a/packages/material-ui/src/styles/responsiveFontSizes.test.js
+++ b/packages/material-ui/src/styles/responsiveFontSizes.test.js
@@ -1,5 +1,6 @@
 import { expect } from 'chai';
 import { createTheme } from '@material-ui/core/styles';
+import defaultTheme from './defaultTheme';
 import responsiveFontSizes from './responsiveFontSizes';
 
 describe('responsiveFontSizes', () => {
@@ -21,9 +22,11 @@ describe('responsiveFontSizes', () => {
     expect(typography.h1).to.deep.equal({
       ...defaultVariant,
       fontSize: '3.5rem',
-      '@media (min-width:600px)': { fontSize: '4.75rem' },
-      '@media (min-width:960px)': { fontSize: '5.5rem' },
-      '@media (min-width:1280px)': { fontSize: defaultVariant.fontSize },
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { fontSize: '4.75rem' },
+      [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: { fontSize: '5.5rem' },
+      [`@media (min-width:${defaultTheme.breakpoints.values.lg}px)`]: {
+        fontSize: defaultVariant.fontSize,
+      },
     });
   });
 
@@ -48,9 +51,11 @@ describe('responsiveFontSizes', () => {
     expect(typography.h1).to.deep.equal({
       ...defaultVariant,
       fontSize: '3.5rem',
-      '@media (min-width:600px)': { fontSize: '4.6719rem' },
-      '@media (min-width:960px)': { fontSize: '5.375rem' },
-      '@media (min-width:1280px)': { fontSize: defaultVariant.fontSize },
+      [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { fontSize: '4.75rem' },
+      [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: { fontSize: '5.375rem' },
+      [`@media (min-width:${defaultTheme.breakpoints.values.lg}px)`]: {
+        fontSize: defaultVariant.fontSize,
+      },
     });
   });