Go to full code example

A very common design component on the web is a grid of items. Over the years there have been many different ways of achieving a grid layout from tables to floats to flexbox and now CSS grid.

One of the great things about CSS grid is that you can create a flexible grid layout with very minimal CSS. In the example below (from Rachel Andrew's Flexible sized grids with autofill and minmax) the number of columns is dependant on how many 200px items will fit in the space on any particular screen size.

.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}

When working on a larger codebase, it's often useful to abstract certain patterns away to help maintain consistency and to prevent solving the same problem more than once. In this case, we can use a sass mixin to generate the css for a flexible grid.

@mixin grid($itemSize, $gap) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax($itemSize, 1fr));
grid-gap: $gap;
}

To use this mixin you add it on the element containing the grid.

.grid {
@include grid(200px, 20px);
}

There is one issue with this fluid grid approach. When using the minmax function in the grid-template-columns property we're passing a minimum size for each of the grid items. If this minimum size happens to be bigger than the browser window then the content won't wrap like we'd expect.

.grid {
@include grid(500px, 20px);
}

To fix this, we can use a media query to only let the grid-template-rows property to be set at a minimum browser width. We want to keep the display: grid and grid-gap rules as these will still be needed to provide vertical space between grid items on smaller screens.

The media query value is difficult to determine as any padding on the grid container will factor into the calculation. The safest approach is to use double the grid item width so it takes effect as late as possible.

@mixin grid($itemSize, $gap) {
display: grid;
@media (min-width: $itemSize * 2) {
grid-template-columns: repeat(auto-fill, minmax($itemSize, 1fr));
}
grid-gap: $gap;
}

We can also add the ability to set the column and row gap independently. We can do this by adding a final optional argument which explicitly sets the row gap size. If no value is given then it will be the same as the column gap.

/* Generates a flexible grid.
* @param {string} $itemSize - minimum grid item size (accepts px, em, rem)
* @param {string} $columnGap - column gap for grid items (accepts any css length unit)
* @param {string} $rowGap (optional) - row gap for grid items (accepts any css length unit). If not set, will inherit the column gap value.
*/

@mixin grid($itemSize, $columnGap, $rowGap: $columnGap) {
display: grid;
@media (min-width: $itemSize * 2) {
grid-template-columns: repeat(auto-fill, minmax($itemSize, 1fr));
}
grid-column-gap: $columnGap;
grid-row-gap: $rowGap;
}

Here's some examples of how to use the mixin. See the comments in the mixin for the values which each argument can accept.

.grid {
/* Generates a flexible grid with minimum 200px wide grid items with a 1rem column and row gap */
@include grid(200px, 1rem);
}
.grid {
/* Generates a flexible grid with minimum 20rem wide grid items with a 15em column and row gap */
@include grid(20rem, 15em);
}
.grid {
/* Generates a flexible grid with minimum 25em wide grid items with a 1rem column gap and a 30px row gap */
@include grid(25em, 1rem, 30px);
}

See this working on Codepen: flexible grid mixin

Extending the mixin

For more complex grid layouts we can use the mixin as a base and add in additional css rules to achieve different effects.

.grid {
@include grid(250px, 20px);
grid-auto-flow: dense;
}

.grid-item {
&:nth-child(3n + 1) {
grid-column: span 2;
grid-row: span 2;
}
}

See this working on Codepen: flexible grid mixin - images