Skip to content

How to: Write responsive pages using Material UI

mmbutton edited this page Aug 14, 2020 · 1 revision

This page is a summary & example of material's documentation. All of the information can be found in

https://material-ui.com/customization/breakpoints/
https://material-ui.com/components/use-media-query/
https://material-ui.com/components/grid/#grid
https://material-ui.com/components/hidden/

Material UI uses Grids, Breakpoints and Media Queries to simplify responsive design. Breakpoints are used to define when a screen should change layout or render in a different way. Grids use breakpoints to determine layout based on breakpoints whereas Media queries determine logical programmatic changes.

Most cases you will only need to use 1 or 2 of these techniques but I've made the below examples build off of each other to demonstrate how these techniques interact.

Breakpoints

Breakpoints define barriers for when the page changes. They can be directly translated to pixel values. We are currently using the material defaults for all our code.

xs, extra-small: 0px
sm, small: 600px
md, medium: 960px
lg, large: 1280px
xl, extra-large: 1920px

Grids

Grids are the most common form of responsiveness. They can be nested inside other components, themselves and/or used as the layout of the entire page. Grid containers divide up their area area into 12 equal columns. Grid items define how many columns they take up at each breakpoint.

Example - A grid structure that changes layout 3 times based on page size.

import React, { Component } from 'react';

import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';

class Example extends Component {
  render() {
    return (
      // This grid layout creates 3 items that change layout responsibly.  At the smallest page size each Paper takes up the entire width
     // of the container. In between sm and lg the first paper is full width but the next two are half width (6/12 columns). At the largest breakpoint
      // they are all on the same row taking up a third of the column (4/12 columns) each
      <Grid container spacing={3}>
        <Grid item xs={12} sm={12} lg={4}>
          <Paper>Paper1</Paper>
        </Grid>
        <Grid item xs={12} sm={6} lg={4}>
          <Paper>Paper2</Paper>
        </Grid>
        <Grid item xs={12} sm={6} lg={4}>
          <Paper>Paper3</Paper>
        </Grid>
      </Grid>
    );
  }
}

export default Example;

Hidden

Hidden components allow you to define breakpoints to conditionally render it's children.

Hidden components are rather new. The rest of our code base uses mediaQueries to do the same function however this has a much simpler interface.

import React, { Component } from 'react';
import Hidden from '@material-ui/core/Hidden';

import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';

class Example extends Component {
  render() {
    return (
      <Grid container spacing={3}>
        <Grid item xs={12} sm={12} lg={4}>
          <Paper>Paper1</Paper>
        </Grid>
        <Grid item xs={12} sm={6} lg={4}>
          <Paper>Paper2</Paper>
        </Grid>
		<Hidden smDown>
          <Grid item xs={12} sm={6} lg={4}>
            <Paper>Paper3</Paper>
          </Grid>
        </Hidden>
      </Grid>
    );
  }
}

export default Example;

CSS Media Queries

CSS media queries allow CSS to dynamically change based on the size of the screen. They should be used when padding, alignment or any other CSS property should change based on screen size.

The most common use case is changing spacing between elements based on screen size. Larger layouts like Desktops usually have more white space than smaller screens such as phones.

Example - Changing padding around a page

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';

import Grid from 'BaseComponents/Grid';
import Paper from 'BaseComponents/Paper';

// The styles constant contains all the CSS for the page. The theme.breakpoints function translates a breakpoint
// into a media query. In this case when the window size is above 960px (over md breakpoint) it has a large amount of whitespace
// around all content. As the page gets smaller the whitespace shrinks under 960px and nearly disappears at 600px (under sm breakpoint)
const styles = theme => ({
  pagePadding: {
    [theme.breakpoints.up('md')]: {
      paddingTop: theme.spacing(8),
      paddingRight: theme.spacing(16),
      paddingLeft: theme.spacing(16),
    },
    [theme.breakpoints.up('sm')]: {
      paddingTop: theme.spacing(4),
      paddingRight: theme.spacing(8),
      paddingLeft: theme.spacing(8),
    },
    [theme.breakpoints.down('sm')]: {
      paddingRight: theme.spacing(),
      paddingLeft: theme.spacing(),
    },
  },
});
class Test extends Component {
  static propTypes = {
    classes: PropTypes.shape({
      pagePadding: PropTypes.string,
    }),
  };

  static defaultProps = {
    classes: {},
  };

  render() {
    const { classes } = this.props;
    return (
      <Grid className={classes.pagePadding} container spacing={3}>
        <Grid item xs={12} sm={12} lg={4}>
          <Paper>Paper1</Paper>
        </Grid>
        <Grid item xs={12} sm={6} lg={4}>
          <Paper>Paper2</Paper>
        </Grid>
        <Grid item xs={12} sm={6} lg={4}>
          <Paper>Paper3</Paper>
        </Grid>
      </Grid>
    );
  }
}

export default withStyles(styles)(Test);

JS Media Queries

Javascript media queries change logical behaviour based on screen size. This should be used when anything in the javascript needs to change based on screen size except for conditional rendering which should use Hidden as shown above.

Media queries are only available as Hooks. Read up on Hooks before trying to implement a JS Media Query.

Example:

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles, useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';

import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';

const styles = theme => ({
  pagePadding: {
    [theme.breakpoints.up('md')]: {
      paddingTop: theme.spacing(8),
      paddingRight: theme.spacing(16),
      paddingLeft: theme.spacing(16),
    },
    [theme.breakpoints.up('sm')]: {
      paddingTop: theme.spacing(4),
      paddingRight: theme.spacing(8),
      paddingLeft: theme.spacing(8),
    },
    [theme.breakpoints.down('sm')]: {
      paddingRight: theme.spacing(),
      paddingLeft: theme.spacing(),
    },
  },
});
const Test = props => {
  // This section of code changes the spacing of the grid based on breakpoint. If the screen is
  // smaller than 600px there is little spacing between elements. As the screen gets larger the
  // the spacing increases to add more whitespace.
  const theme = useTheme();
  let spacing = 4; // default spacing for above md breakpoint
  if (useMediaQuery(theme.breakpoints.down('md'))) {
    spacing = 2;
  }
  if (useMediaQuery(theme.breakpoints.down('sm'))) {
    spacing = 1;
  }

  const { classes } = props;
  return (
    <Grid className={classes.pagePadding} container spacing={spacing}>
      <Grid item xs={12} sm={12} lg={4}>
        <Paper>Paper1</Paper>
      </Grid>
      <Grid item xs={12} sm={6} lg={4}>
        <Paper>Paper2</Paper>
      </Grid>
      <Grid item xs={12} sm={6} lg={4}>
        <Paper>Paper3</Paper>
      </Grid>
    </Grid>
  );
};

Test.propTypes = {
  classes: PropTypes.shape({
    pagePadding: PropTypes.string,
  }),
};

Test.defaultProps = {
  classes: {},
};

export default withStyles(styles)(Test);