Create your design system, part 2: Grid & Layout

Choosing how to handle layouts and content positioning is probably one of the first decision you’re going to make when building a design system.

Defining the grid means creating the system to structure your content, whether it’s a single component or a page layout.

In this article, we are going to see how a grid system can be implemented, and how some CSS techniques can be used to create specific layouts.

Here’s what we will cover:
1) Layout with an auto-generated number of columns — using CSS Grid;
2) One-dimensional layout — using Flexbox;
3) Two-dimensional layout — using CSS Grid;
4) Two-dimensional layout with overlapping elements — using CSS Grid.

This article is part of a series on design systems inspired by our (upcoming) library of web components. The library relies on a solid system of CSS globals. So this is us sharing the things we’ve learned setting the global style of our library! ✌

Article Series:
- Part 1: Typography
- Part 2: Grid & Layout
- Part 3: Colors
- Part 4: Spacing
- Part 5: Icons
- Part 6: Buttons

1 — Layout with an auto-generated number of columns

Let’s say you have a gallery of products you want to lay out in a grid: all your items need to have the same width (they are instances of the same ‘product’ component, so they have the same dimension) and have a minimum width so that the design does not break; assume you want, at different screen sizes, the maximum number of items per row; something like the CodyHouse library (resize the page to see the change in number of items).

It would be ideal to find a way to automatically determine this maximum number of items without having to add new CSS code at different media queries.

This can be done using CSS Grid.

Let’s start by creating a grid mixin (we’ll be reusing it also for the layouts 3 and 4).

This mixin is used to initialize the grid container (with display grid) and set the grid gap (which is the free space between two adjacent items).

We have included the CSS Grid code inside a @supports rule to target browsers that support the Grid (leaving out also the browsers that support the old CSS Grid specification, like IE 11).

Now we can define the mixin that will create our gallery; the min-width of the items will be the only argument of this mixin:

The minmax function allows us to set a min-width for our elements, while the repeat() function takes care of actually creating the grid.

We can now use these mixins like that:

And here’s an example of these mixins in action:

The mixins we defined above won’t work in IE and older versions of Edge (<= 15). Your gallery would still be accessible, but your items would show up one per row (100% width).

If you need to provide a better fallback, then you can use the float property to recreate your gallery, but you won’t be able to change the number of items per row at different screen sizes: you’ll need to set a fixed number of items per row (this number will be passed as the second argument to the gridAuto() mixin).

Here’s what the grid mixin becomes with the fallback addition:

The CSS properties defined inside the @supports rule are the ones applied when the browser supports CSS Grid (the new specification); while the properties defined outside the @supports are applied in all browsers (and this is why, inside the @supports rule, we had to add some additional style to overwrite those properties).

The gridAuto() mixin becomes:

The mixin now accepts two arguments: the first one is the minimum width of the items in the gallery (the same as before — it will be used only if the browsers support CSS Grid), the second (which is optional — default value is 3) is the number of items per row on browsers that do not support CSS Grid (where the fallback is applied).

2— One-dimensional layout

This is probably the easiest layout we can create: we have items we want to arrange in columns, with the option of customizing their width and still being able to distribute the space among them equally.

There are few techniques we can use to implement this kind of layout. We are going to use Flexbox in combo with utility classes to customize the items width.

This approach has been around for a long time. It’s handy, but if you don’t want to use utility classes in your HTML (something like col — 1, col — 5, …), then a different method is the one I describe in section 3, where we create the same layout using CSS Grid.

Before defining our grid classes, let’s set a variable that we will use as grid gap:

Let’s define a class for our grid container:

We initialize our flex container (using display flex) and allow the children to wrap on multiple lines if needed (using the flex-wrap property). 
The negative margins are added to balance the padding we use to create the grid gap (see .col class definition below) so that no empty space is left between the .flex-grid element and its container.

Our grid items will have a .col class:

We use the padding to create the gap between elements and the background-clip so that the background-color/image of the .col element is not applied to the padding (leaving the gap visible).

You don’t need to use the background-clip property if you are not planning on adding a background-color/image to your .col element (or if the .col element has a child you can apply this background style to).

By default, all .col items have a 100% width (flex-basis 100%). We can use classes to change that width value:

The round-width function is used to round the columns width to a number with 2 decimal places (this prevents the layout from breaking in IE).

This creates the classes .col — 1 up to .col — 12 (you can change the value of the $grid-columns variable if you are not using a 12 unit-grid).

If you want to create a layout with two elements, the first taking up 9 of the 12 available columns and the latter the remaining 3, you can use something like:

You can also define different classes for different media queries if you want to change the width of your elements at different screen sizes. Something like:

Here’s an example of the flex classes in action:

We’ve decided to include this grid system in our library, because of its ease of use. It’s optional though, you can use the method described next (which does not rely on utility classes) if you prefer.

3 — Two-dimensional layout

In layout 2, we considered the case where we needed to control the width of the elements in our row. We didn’t consider the height of the elements at all.

If we want to create a two-dimensional layout where we can control the height of our elements as well, then CSS Grid is probably the best solution.

Here’s an example of a layout you can create using this technique:

We’ll be reusing the grid mixin (defined in section 1) and we’ll be adding a second gridLayout() mixin. 
This new mixin will accept, as argument, a list of pairs of numbers:

For each element in your layout, you’ll have to pass a pair of numbers (in the example above, I’m passing 4 pairs which means our layout is composed of 4 elements). For each couple of numbers, the first will be the number of columns the element has to occupy, the second the number of rows.

In the code above, we are saying: the first element in the layout has to take up 7 columns and 2 rows; the second element 5 columns and 1 row; the same for the third element; the last one 12 columns (100% width) and 1 row.

Let’s see what the mixin looks like:

First, we use grid-template-columns to define our grid of columns; this will create a template of 12 columns, all having the same width.

Notice we haven’t defined the grid-template-rows (or grid-auto-rows). This is mostly because the row height depends on the type of content you want to show: you can decide to have rows of a fixed height, rows that are a percentage of the viewport or just leave your content determine the height. You can specify this in your class when calling the mixin.

The ‘each’ loop is where the items are allocated: for each one of the pairs passed to the mixin, we take the corresponding element (using the :nth-of-type selector) and place it in our grid (using the span keyword).

Here’s an example of the mixin in action:

Note: the second number in each pair is not required (in the mixin, we check if the second value is passed before setting the grid-row-end property).

Passing just one number per item will allow you to create a one-dimensional layout; this is an alternative to the method described in section 2 (Flexbox + utility classes).

We can modify the gridLayout() mixin to add a fallback for browsers that do not support CSS Grid. Just keep in mind that, with the fallback, you won’t be able to control the height of your items.

4 — Two-dimensional layout with overlapping elements

This is quite a specific layout case: let’s say you want to create a two-dimensional layout (as we did with case 3 where you can set both width and height of your items) but you want to control the start/end position of your elements as well (so that they can overlap).

With the gridLayout() mixin, the items are automatically placed in the grid with no overlapping. You won’t be able to create something like this:

We can create a new gridAdvanced() mixin which will bring our layout a step further; here’s how we’re going to use it:

This time, we’ll need to pass, for each element in our layout, four numbers: the first two are the item’s start and end position within the grid columns, while the last two the row start and end position.

In the example above, we have 3 elements: the first one starts at column 1 and ends at column 8 (which means, it takes up 7 columns in our layout -> remember column 8 marks the end of the element, so it is not included) and starts at row 1 and ends at row 3 (2 rows); the second one takes the same columns but it starts at row 3 and ends at row 5 (2 rows); the third one starts at column 5 and takes up all the remaining columns (-1 means go till column 12 but include it as well) and starts at row 2 and ends at row 4 (2 rows).

Here’s our mixin:

This is quite similar to the gridLayout() one; this time, though, we are using the grid-column(/row)-start/end to specify the position of our elements.

Here’s an example of the mixin in action:

We can provide a fallback for this mixin as well; keep in mind that you won’t be able to create the overlapping effect, as well as customize the items’ height.

That is all I have to share on how we’re setting the grid system for the CodyHouse library. As usual, we’re open to suggestions! Any feedback is welcome. 🙂


I hope you enjoyed the article! For more web design nuggets, follow us here on Medium or Twitter. 🙌