Responsive Design

The guide walks you through handling multiple screen sizes when laying out your UI with React Storefront.

Breakpoints

React Storefront builds upon Material UI's responsive capabilities. Material UI provides 5 breakpoints by default:

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

These are set in the breakpoints.values in the theme.

For more information see Material UI - Breakpoints

Using Breakpoints in CSS

The best way to implement responsive layout in your components is to use the theme to generate breakpoint selectors in your component styles. This is both cache-friendly and SSR compatible. Here's an example where we swap the orientation of the main image carousel and product information for a PDP from vertical to horizontal as the screen size increases past the sm breakpoint:

import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles'
import Typography from '@material-ui/core/Typography'
import Container from 'react-storefront/Container'
import ImageSwitcher from 'react-storefront/ImageSwitcher'
import { inject, observer } from 'mobx-react'

@withStyles(theme => ({
  header: {
    [theme.breakpoints.down('sm')]: {
      flexDirection: 'column',
      alignItems: 'stretch'
    },
    [theme.breakpoints.up('md')]: {
      flexDirection: 'row'
    }
  }
  info: {
    [theme.breakpoints.up('md')]: {
      flex: 3
    }
  },
  images: {
    [theme.breakpoints.down('sm')]: {
      height: 'calc(100vh - 300px)'
    },
    [theme.breakpoints.up('md')]: {
      flex: 1
    }
  }
}))
@inject(({ app }) => ({ product: app.product }))
@observer
export default class Product extends Component {
  render() {
    const { classes, product } = this.props

    return (
      <Container>
        <div className={classes.header}>
          <ImageSwitcher className={classes.images} product={product}/>
          <div className={info}>
            <Typography variant="h6">{product.name}</Typography>
            <Typography>{product.description}</Typography>
          </div>
        </div>
      </Container>
    )
  }
}

Hidden

Material UI's Hidden component provides a handy way to hide specific elements based on viewport width. Always assign implementation="css" so that the result is compatible with server side rendering.

@withWidth

When CSS isn't enough, you can inject the current viewport width into your component as a prop using Material UI's withWidth function.

SSR Limitations

Using @withWidth means that the value of initialWidth in AppModelBase will be used during server-side rendering.

AMP

When rendering AMP content, app.initialWidth is set to "xs". Since Google only delivers AMP pages on mobile devices, React Storefront assumes a small screen size when rendering amphtml.

Caching Implications

When using @withWidth you need to consider how this affects server-side caching. By default, React Storefront caches a single response for each URL, so all devices will get the same initial server side rendering, regardless of screen size.

You can cache multiple responses for different devices based on the user agent by specifying a key function in your routes cache handler:

router.get('/p/:id', 
  fromClient({ page: 'Product' }),
  fromServer('./product/product-handler'),
  cache({ maxAgeSeconds: 300, key: (request, defaults) => {
    const ua = new UAParser(env.user_agent)
    const os = ua.getOS()
    
    return {
      ...defaults,
      initialWidth: os.name.match(/iOS|Android/) ? 'xs' :  'xl'
    }
  }})
)

In the example above, we add initialWidth to the default key generated by React Storefront to create a custom key that allows us to cache separate responses for desktop and mobile user agents.

Responsive Components

React Storefront provides a number of components with built-in responsive behavior.

Container

React Storefront's Container component defines a maximum width for body content and sures that the content remains centered when the viewport exceeds the maximum width. It is recommended that you use a Container as the root component of all of your pages.

The maximum width is determined by theme.maxWidth.

AppBar

AppBar can be configured to automatically hide the hamburger button for desktop devices by setting the responsive prop to true. It also constrains its inner contents to theme.maxWidth.

HeaderLogo

HeaderLogo will automatically center align for mobile devices and left align for desktop devices.

NavTabs

NavTabs constrains its inner contents to theme.maxWidth and provides scrollers on overflow.

ResponsiveTiles

Product listings are often implemented as a set of tiles. The ResponsiveTiles component provides a foundation for this type of UI. It automatically sets the number of columns of tiles based on theme breakpoints.

<ResponsiveTiles>
  { subcategory.items.map((product, i) => (
    <Link key={i} to={`/p/${product.id}`} className={classes.link}>
      <Vbox alignItems="stretch">
        <div className={classes.thumb}>
          <Image eager={index < 4 || index >= 10} aspectRatio={100} alt="product" src={product.thumbnail}/>
        </div>
        <div>
          <Typography variant="subtitle1" className={classes.name}>
            {product.name}
          </Typography>
          <Rating product={product} className={classes.rating}/>
          <Typography className={classes.price}>{ price(product.price) }</Typography>
        </div>
      </Vbox>
    </Link>
  ))}
</ResponsiveTiles>