CSS Grid + Flexbox Solving Real-world Problems

Peter Mouland on 2017-12-11

> Complete CodePen Demos: https://codepen.io/collection/XQdebB/

Recently I had been given a responsive design that looked complex in the way items changed order and layouts changed at different viewports. This new design looked complex, until we thought of it in terms of CSS Grid and FlexBox.

The challenge for this would be supporting Internet Explorer 11 and Safari 9+.

After highlighting the design complexities, we will show the CSS needed to support modern browsers, then we’ll add IE support and finally get it to work in Safari 9 + 10.0.

The design

Responsive Wireframes for desktop, mobile variation A and mobile variation B

Looks pretty straight forward, with a couple points to note:

Building The CSS Grid

We split up the card into different areas.

responsive wireframes with grid overlayed

There were a couple known things we couldn’t use from the spec, namely: grid-column-gap and named grid columns as these were not in the original draft of the spec and Internet Explorer does not support them.

Mobile Grid Layout

This is a pretty straight-forward grid layout. The only thing to note is area-d is also using flex and space-between to keep the buttons at the edges.

product-card-base--mobile-a.css

.product-card {
  display: grid;
  grid-template-rows: repeat(3, min-content);
  grid-template-columns: 33% 1fr 55px;
  grid-auto-rows: min-content;
}

.grid-area--a {
  grid-row: 1 / span 2;
  grid-column: 1;
}

.grid-area--b {
  grid-row: 1;
  grid-column: 2 / span 2;
}

.grid-area--c {
  grid-row: 2 / span 2;
  grid-column: 2;
}

.grid-area--d {
  grid-row: 3;
  grid-column: 1 / span 3;
  display: flex;
  flex-basis: 100%;
  justify-content: space-between;
}

Mobile Grid Layout — Variant B

Small and simple changes to adapt the grid. Also area-d flex direction has changed so that the buttons are now vertically aligned.

product-card-base--mobile-b.css

.product-card--b .grid-area--a {
  grid-row: 1 / span 3;
}

.product-card--b .grid-area--b {
  grid-column: 2;
}

.product-card--b .grid-area--d {
  grid-row: 1 / span 3;
  grid-column: 3;
  flex-direction: column;
  justify-content: space-between;
}

Desktop Grid Layout

product-card-base--desktop.css

@media only screen and (min-width: 768px) {
  .product-card {
    grid-template-rows: repeat(4, min-content);
    grid-template-columns: 100%;
  }
  
  .product-card .grid-area {
    grid-column: 1;
  }
  
  .product-card .grid-area--a {
    grid-row: 1;
  }

  .product-card .grid-area--b {
    grid-row: 2;
  }

  .product-card .grid-area--c {
    grid-row: 3;
  }

  .product-card .grid-area--d {
    grid-row: 4;
  }

  .product-card--b .grid-area--d {
    flex-direction: row;
  }
}

How’s it look so far?

Taking a look at our code so far, our CSS Grid Codepen demo works great in Safari 10.1+, Firefox, Chrome and Opera, but not at all within Internet Explorer or Safari 9 or 10.0.

Internet Explorer

If you are using autoprefixer you will have to explicitly turn grid support on. This will add the -ms- prefixes:

Last step, due to the media queries, is to ensure we specify / span 1. This is because of the expanded syntax, if we forget to specify the span count, any previous -ms-grid-row-span: would still be specified giving unwanted results.

product-card--ie.css

@use autoprefixer {
  remove: false;
  grid: true;
  browsers: "> 1%, firefox 32";
}

.product-card {
  display: grid;
  grid-template-rows: repeat(3, min-content);
  grid-template-columns: 33% 1fr 55px;
  grid-auto-rows: min-content;
}

.grid-area--a {
  grid-row: 1 / span 2;
  grid-column: 1;
}

.grid-area--b {
  grid-row: 1;
  grid-column: 2 / span 2;
}

.grid-area--c {
  grid-row: 2 / span 2;
  grid-column: 2;
}

.grid-area--d {
  grid-row: 3;
  grid-column: 1 / span 3;
  display: flex;
  flex-basis: 100%;
  justify-content: space-between;
}

.product-card--b .grid-area--a {
  grid-row: 1 / span 3;
}

.product-card--b .grid-area--b {
  grid-column: 2 / span 1; /* explicit span for ie */
}

.product-card--b .grid-area--d {
  grid-row: 1 / span 3;
  grid-column: 3 / span 1; /* explicit span for ie */
  flex-direction: column;
  justify-content: space-between;
}

@media only screen and (min-width: 768px) {
  .product-card {
    grid-template-rows: repeat(4, min-content);
    grid-template-columns: 100%;
  }

  .product-card .grid-area {
    grid-column: 1 / span 1; /* explicit span for ie */
  }

  .product-card .grid-area--a {
    grid-row: 1 / span 1; /* explicit span for ie */
  }

  .product-card .grid-area--b {
    grid-row: 2 / span 1; /* explicit span for ie */
  }

  .product-card .grid-area--c {
    grid-row: 3 / span 1; /* explicit span for ie */
  }

  .product-card .grid-area--d {
    grid-row: 4 / span 1; /* explicit span for ie */
  }

  .product-card--b .grid-area--d {
    flex-direction: row;
  }
}

CodePen Demo for Internet Explorer

Tackling Safari 9 + 10.0

Safari < 10.1 doesn’t support CSS Grid. At all. This is a shame, and unfortunately layout, isn’t ‘progressive enhancement’ — it’s pretty essential. But, I would say, maybe we don’t aim for ‘perfection’; instead let’s see how close we can get.

Safari 9 does understand @supports, unfortunately, Internet explorer doesn’t. This means we had to come up with an alternate. As we’re using PostCSS I came up with a mixin. This will unfortunately bloat the CSS some-what.

undoSafariHacks.css

@use postcss-mixins;
@define-mixin undoSafariHacks {
  /* target ie11 only as this doesn't understand `@supports` */
  @media (-ms-high-contrast: active), (-ms-high-contrast: none) {
    @mixin-content;
  }

  /* target browsers that support `@supports` */
  @supports (display: grid) {
    @mixin-content;
  }
}

Using the same Grid Layout concept, we have to add floats, widths + margins in order to closely (but not exactly) replicate the more modern browsers. The one sacrifice I’ve decided to make is to not replicate mobile layout b on Safari.

product-card--safari.css

@use postcss-mixins;
@use autoprefixer {
  remove: false;
  grid: true;
  browsers: "> 1%, firefox 32";
}

@define-mixin undoSafariHacks {
  /* target ie11 only as this doesn't understand `@supports` */
  @media (-ms-high-contrast: active), (-ms-high-contrast: none) {
    @mixin-content;
  }

  /* target browsers that support `@supports` */
  @supports (display: grid) {
    @mixin-content;
  }
}

.product-card {
  display: grid;
  grid-template-rows: repeat(3, min-content);
  grid-template-columns: 33% 1fr 55px;
  grid-auto-rows: min-content;
  align-content: start; /* fix macOS safari 10 */
}

.grid-area--a {
  grid-row: 1 / span 2;
  grid-column: 1;
  float: left; /* set for < Safari 10.1 */
  display: inline-block; /* set for < Safari 10.1 */
  width: 33%; /* set for < Safari 10.1 */
}

.grid-area--b {
  grid-row: 1;
  grid-column: 2 / span 2;
  display: inline-block; /* set for < Safari 10.1 */
  width: 66%; /* set for < Safari 10.1 */
}

.grid-area--c {
  grid-row: 2 / span 2;
  grid-column: 2 / span 1;
  display: inline-block; /* set for < Safari 10.1 */
  width: 66%; /* set for < Safari 10.1 */
  float: right; /* set for < Safari 10.1 */
  margin-left: 33%; /* set for < Safari 10.1 */

}

.grid-area--d {
  grid-row: 3;
  grid-column: 1 / span 3;
  display: flex;
  flex-basis: 100%;
  justify-content: space-between;
  width: 100%; /* set for < Safari 10.1 */
}

.product-card--b .grid-area--a {
  grid-row: 1 / span 3;
}

.product-card--b .grid-area--b {
  grid-column: 2;
}

.product-card--b .grid-area--d {
  grid-row: 1 / span 3;
  grid-column: 3;
  flex-direction: row; /* set for < Safari 10.1 */
  justify-content: space-between;
}

@mixin undoSafariHacks {
  .grid-area--a {
    width: auto;
    float: none;
    display: block;
  }
  .grid-area--b {
    width: auto;
    display: block;
  }
  .grid-area--c {
    float: none;
    width: auto;
    margin-left: 0;
  }
  .product-card--b .grid-area--d {
    flex-direction: column;
  }
}

@media only screen and (min-width: 768px) {
  .product-card {
    display: block;
  }

  .product-card .grid-area {
    float: none; /* set for < Safari 10.1 */
    width: 100%; /* set for < Safari 10.1 */
    margin: 0; /* set for < Safari 10.1 */
  }

  .product-card--b .grid-area--d {
    flex-direction: row;
  }
}

Due to supporting browsers that don’t understand CSS Grid, we’ve now had to add more extra code than I would like, but, I feel it’s been worth it to get a better layout for most users.

The Cost of Browser Support

We’ve gone from 1.39KB to 2.76kb. If we treat Internet Explorer like Safari and drop the -ms- prefixes, we are back down to 1.96kb.

These numbers are all small so it’s depends the situation and whether or not you want the maintenance overhead as to which option you take.

TL;DR

The CSS Grid helps solve complex layout problems and changes how you may view these problems from the beginning — creating simpler code!

All CodePen Demos: https://codepen.io/collection/XQdebB/