Build complex layouts with PostCSS-Flexbox

Flexbox is a great tool for reducing CSS bloat, and has some sugar built in to deal with things like source order and alignment. But it doesn't offer any help for doing the maths that creates column and gutter sizes. As many experts repeatedly state: Flexbox is nice, but it's not a grid system. 

We should use Flexbox, but we shouldn't throw away the basic concepts behind grid systems that pulled us out of the 'GeoCities era' of design:

  • Make columns proportional to something
  • Use consistent gutters throughout your design

We can use one of the many Flexbox grid systems out there, but in all honesty, the styles behind these are mostly the same as float-based grids. Authors just add  display: flex  to the container, along with some helper classes that obfuscate Flexbox properties (for example, a class like .grid-vertical-align-mid is an obfuscation of align-items: center).

The only difference is, now columns are 'flexed', which means we get that sugar I mentioned earlier. But this still doesn't solve the main problems that grids face today: grid systems still output a ton of CSS bloat, and grids are still boring.

The problem with grids

System bloat: Bootstrap’s generated CSS for the grid alone is 16,435 characters – most of which you’ll never use

Grid authors typically distribute huge packs of CSS that can offset, source order, align and resize according to some arbitrary device size. They rely on gzip to mitigate some of the damage, but this doesn't get it all. What if you don't need those extra features?

Assuming the framework you're currently using offers grid mixins, the best solution might be to learn those and compose your own columns. This produces little bloat, but limits your grids to fractions and doesn't offer you a lot of mobility between different frameworks.

On to the second issue. Designers have become mentally locked in to making all their decisions based on a certain combination of equally sized columns. As a result, the entire internet is beginning to blur together into a 'stack on mobile; 1/12 column combinations on anything else' dystopia.

Flexbox introduced flex-grow, which freed us a little. Now we can set sizes that look good and fill in the remaining space with a stretched element, but do gutters line up consistently throughout the page? Are content areas laid out with any sort of proportions in mind, or are we back to just winging it?

Enter the dragon

The solution to both these problems is wrapped up in a function called postcss-ant. PostCSS plugins can be used with a range of build tools like webpack, gulp or simply the command line. ant mixes seamlessly into plain CSS (or any preprocessor) with a deceptively simple API that masks dozens of mind-boggling calc formulas. It requires two mixin-like parameters to fetch a plethora of grid-friendly sizes. 

An example might make this clearer:  width: sizes(1/2) get(1) returns calc(99.99% * 1/2 - (30px - 30px * 1/2)). Let's break this down: 

  • sizes() is a space-separated list of sizes. These can be any combination of a variety of sizes: fixed numbers in the form of valid CSS lengths (px, em, %); fractions or float numbers (which return whatever is leftover, sans fixed numbers); and auto keywords (which return whatever size is left over, sans fractions and fixed numbers)
  • pluck() is a 1-based index that fetches a grid-friendly size from your list. So sizes(1px 2px 3px) get(2) would return 2px
  • The calc formula is a grid-friendly size

We get the  width  for each column in a two-column grid with a single  30px  gutter between them:

.grid {
  display: flex;
  flex-wrap: wrap;
}

.half {
  width: sizes(1/2) get(1);  /* returns calc(99.99% * 1/2 - (30px - 30px * 1/2)) */
  margin-right: 30px;  /* puts a gutter on the right side of each column */
}

.half:nth-child(2n + 2) {  /* starts at the 2nd element and begins counting by 2s */
  margin-right: 0;  /* removes the last gutter from every row */
}

This already gives designers the power to use these sizes wherever they see fit, but let's dive deeper. 

sizes(100px 1/2) get(1) returns 100px. Simple enough, but why would we need to use ant to return 100px ? We'll get to that in a second. 

 sizes(100px 1/2) get(2) returns calc((99.99% - (100px + (30px * 1))) * 1/2 - (30px - 30px * 1/2)). Holy crap. ant is finding out the total size of fixed numbers, and then returning 1/2 of whatever is left over – in an nth grid-friendly way. 

We can use these calc formulas to create a grid with a 100px column and two 1/2 columns like so (I'll be omitting the .grid styles to save trees, but be sure to include them in your code):

.fixed-size {
  width: sizes(100px 1/2) get(1); /* returns 100px (I know it seems silly but bear with me for a bit longer) */
  margin-right: 30px;
}

.half {
  width: sizes(100px 1/2) get(2);  /* returns calc((99.99% - (100px + (30px * 1))) * 1/2 - (30px - 30px * 1/2)) */
  margin-right: 30px;
}

.half:nth-child(3n + 3) {
  margin-right: 0;
}

Now we can get some previously unachievable sizes, but it's not super-flexible yet and can require a lot of writing when dealing with many sizes.

Preprocessor looping

Minimal markup: A minimal markup, two-column, nth grid using postcss-ant. Notice how there is no margin-right on the last element in the row

Preprocessors and PostCSS don't always get along – especially when custom syntax is involved. Luckily, ant's API is preprocessor-friendly. We can use a PostCSS-parser like postcss-scss with a PreCSS plugin, but this approach uses a lot of unfinished/unmaintained PostCSS plugins that poorly mimic Sass' functionality. I've found the best workflow is to:

  • Use a fast preprocessor like node-sass to watch in.scss to out.css
  • Use PostCSS to watch out.css to final.css
  • <link> to final.css in your markup

This gives you the best of preprocessor syntax with all the PostCSS plugins your heart desires.

Now for the cool stuff. Preprocessors typically have a way to loop a specified number of times while providing an iterator:

@for $i from 1 through 3 {
  content: $i;  // returns content: 1; content: 2; content: 3;
}

With a little knowledge of preprocessors, you can begin using ant in really interesting ways ...

$sizes: 100px 1/2 1/2;
$gutter: 30px;
$length: length($sizes);

.column {
  margin-right: $gutter;

  @for $i from 1 through $length {
    &:nth-child(#{$length}n + #{$i}) {
      width: sizes($sizes) get($i) gutter($gutter);
    }
  }

  &:nth-child(#{$length}n + #{$length}) {
    margin-right: 0;
  }
}

Now our irregularly sized columns wrap to new rows without additional markup. Go ahead and tinker around with this code. Try varying sizes, gutters, and adding new sizes (like 100px auto 100px).

This is a pretty common pattern with ant, so I've wrapped it up into a property that works with vanilla CSS as well:

.grid {
  generate-grid: sizes(100px 1/2 1/2); /* You can pass additional options, but pluck() is no longer needed. */
}

Lots of options

Preprocessor loops: ant really begins to show its strength when we combine it with preprocessor loops to create layouts that used to be impossible

nth grids are great when you know the size and number of elements to be passed, but sometimes you'll want to create a container and just dump some randomly sized elements in there. In these cases, negative-margin grids are your best bet. Simply pass negative-margin to ant like so:

.grid {
  margin: 0 -15px;
}

.column {
  width: sizes(1/3) get(1) grid(negative-margin);
  margin: 0 15px;
}

Conclusion

These examples were just a tiny glimpse at all the cool things postcss-ant can do. It has a couple more optional parameters and lots of descriptive error handling, but ant's true power is you.

'Dumb' tools that simply return a bit of data are always the best tools, as they put the power back into the developer's hands. That is what postcss-ant was designed to do. If you're intrigued, head to corysimmons.github.io/postcss-ant for some face-melting demos and sweet docs.  

This article was originally published in issue 286 of net magazine, buy it here.

Related articles: