Roll your own flexible grid system

DIY is cooler than off the shelf.

Gav McKenzie
Author
Gav McKenzie
Published
Aug 29, 2018
Topics
How-to, Resource, Industry

Do you install Bootstrap just for the grid? Maybe you use another grid system? Why not have your own? Learn to make a flexible flexbox flex grid.

This tutorial assumes you know a bit about React and Sass. If not, go pick up the basics at one of the many great tutorials.

Why?

Using other peoples grids can be frustrating. They’re never quite right for what you want and then it’s difficult to get at the code to update them.

Rolling your own only takes about 30 minutes and then it’s ready to use anywhere in your project!

We’re going to be using React and SCSS to create a flexible, flexbox based, grid with tons of power.

Demo and Repo

You probably just want to look at this code and fire up the demo right?

OK, moving on…

FlexGrid

Let’s start with the outer wrapper. We’ll need a container for our grid, which can also be used to force rows if we need to.

The JavaScript

import PropTypes from 'prop-types';
export default function FlexGrid({ children }) {
return (
<div className="flex-grid">
{children}
</div>
);
}

Here’s a simple stateless component that we can use to start our grid component.

We’re going to try to expose as much of the raw flexbox API as possible from this component. We’ll start with a list of parent properties from this excellent article by CSS Tricks.

  • flex-direction (row, row-reverse, column, column-reverse)

  • align-items (stretch, flex-start, flex-end, center, baseline)

  • justify-content (flex-start, flex-end, center, space-between, space-around)

  • flex-wrap (wrap, nowrap)

We need to turn these CSS props into React props so we can turn them into CSS classes so we can turn them back into CSS props (Ahh, the modern internet is glorious…).

We’ve got 3 strings: direction, align, justify; and 1 boolean: wrap.

We’ll use the classnames module from npm to generate custom classes for the strings and an on/off wrap class for the boolean. I’m using BEM because I like the way it keeps things name-spaced and human readable, but feel free to pick your favourite flavour of CSS organisation.

function Grid({ children, direction, align, justify, wrap, className, ...props }) {
const cx = classNames({
'flex-grid': true,
[`flex-grid--${direction}`]: direction !== '',
[`flex-grid--align-${align}`]: align !== '',
[`flex-grid--justify-${justify}`]: justify !== '',
'flex-grid--wrap': wrap,
[`${className}`]: className !== '',
});
return (
<div {...props} className={cx}>
{children}
</div>
);
}

I’ve tagged on a custom className prop just in case you need a little something extra, but I wouldn’t recommend using this much.

Finally we use the object rest spread to pass through any other random props that might need adding to this component.

Each of these classes is essentially a direct map to a CSS flexbox property, so let’s dive into the CSS.

The CSS

We’ll start off with the base CSS to power the container.

.flex-grid {
display: flex;
margin: (-$grid-gutter-width / 2);
}

The negative margin is to offset the padding we’re going to add to our flex grid items. This will keep our grid edges lined up with everything else, but our cells nicely spaced apart, both vertically and horizontally.

Next up, we’ll start powering through the different optional classes. I’ll just show a snippet here, but you can grab the full set in the repo.

.flex-grid {
display: flex;
margin: (-$grid-gutter-width / 2);
&--wrap {
flex-wrap: wrap;
}
&--direction {
&-row {
flex-direction: row;
&-reverse {
flex-direction: row-reverse;
}
}
&-column {
flex-direction: column;
&-reverse {
flex-direction: column-reverse;
}
}
}
}

FlexGrid.Item

We have a grid wrapper, now we need grid cells, or items.

The JavaScript

The FlexGrid item is virtually the same setup as the flex grid outer wrapper, but with a new set of props.

function Item({ children, grow, shrink, align, flex, className, ...props }) {
const cx = classNames({
'flex-grid__item': true,
'flex-grid__item--grow': grow,
'flex-grid__item--shrink': shrink,
'flex-grid__item--flex': flex,
[`flex-grid__item--align-${align}`]: align !== '',
[`${className}`]: className !== '',
});
return (
<div {...props} className={cx}>
{children}
</div>
);
}

There’s one prop here that is new and not part of the flexbox API for flex children.

The ‘flex’ prop sets display: flex on this flex child. This is super useful when you want all the items to line up vertically with the same height. You might need to jiggle your child component a little here, but it will get you a long way. Don’t forget to set 100% width!

We’re going to want to have some control over the column widths in this component so we’ll add an extra set of props for: xs, sm, md and lg;

function Item({ children, grow, shrink, align, xs, sm, md, lg, flex, className, ...props }) {
const cx = classNames({
'flex-grid__item': true,
'flex-grid__item--grow': grow,
'flex-grid__item--shrink': shrink,
'flex-grid__item--flex': flex,
[`flex-grid__item--align-${align}`]: align !== '',
[`flex-grid__item--xs-${xs}`]: xs,
[`flex-grid__item--sm-${sm}`]: sm,
[`flex-grid__item--md-${md}`]: md,
[`flex-grid__item--lg-${lg}`]: lg,
[`${className}`]: className !== '',
});
return (
<div {...props} className={cx}>
{children}
</div>
);
}

For maximum convenience, we’ll attach this to the main export from FlexGrid. You can keep it in the same file or a different file depending on your organisational preferences.

Grid.Item = Item;

export default Grid;

The CSS

Now we have the markup sorted and generating classes, we can add some CSS. Again, most of the options correspond to a single flexbox property.

&__item {
flex: 0 0 auto;
min-width: 0; // Fixes a bug not letting the child shrink the content
padding: $grid-gutter-width / 2;
&--grow {
flex-grow: 1;
}
&--shrink {
flex-shrink: 1;
}
&--flex {
display: flex;
}
&--align {
&-flex-start {
align-self: flex-start;
}
&-flex-end {
align-self: flex-end;
}
&-center {
align-self: center;
}
&-baseline {
align-self: baseline;
}
&-stretch {
align-self: stretch;
}
}
}

There’s a few notable base styles here. We explicitly set all the basic flex properties with the shorthand flex: 0 0 auto;

This is short for:

flex-grow: 0;
flex-shrink: 0;
flex-basis: auto;

We also set min-width: 0; as there is a bug with flex shrink which prevents it from squishing it’s inner content unless you specifically tell it that it can be zero width.

Finally, we set a nice gutter as mentioned up in the FlexGrid wrapper component above.

In order to create the columns, we’re going to use the breakpoints Sass map to generate a bunch of column classes for us.

$bp: (
xs: 0,
sm: 480px,
md: 768px,
lg: 1024px
) !default;

I’ve set these up with some dirty pixels, but you’d probably be better off using ‘em’s.

Using a sass map lets us loop through the key/value pairs and create some awesome responsive grid classes. This example uses a 12 column grid, but you could use as many columns as makes you happy.

[@each](http://twitter.com/each) $bp-key, $bp-value in $bp {
[@media](http://twitter.com/media) screen and (min-width: $bp-value) {
[@for](http://twitter.com/for) $i from 1 through 12 {
&--#{$bp-key}-#{$i} {
width: percentage($i / 12);
}
}
}
}

The classes are generated in such a way that passing md=6would make the grid item span 6 columns out of 12 (50%) at the md breakpoint (>768px).

Let me use it

And that’s it! You should now have a customisable, super flexible, super powerful grid system that you can use in your app.

<FlexGrid wrap>
<FlexGrid.Item xs={12} md={6} lg={3}>
Item
</FlexGrid.Item>
<FlexGrid.Item xs={12} md={6} lg={3}>
Item
</FlexGrid.Item>
<FlexGrid.Item xs={12} md={6} lg={3}>
Item
</FlexGrid.Item>
<FlexGrid.Item xs={12} md={6} lg={3}>
Item
</FlexGrid.Item>
</FlexGrid>

TL;DR

Use this repo as a starter to make your own flexbox grid system, it’s easy, honest!