Position elements outside of your container with ease using a 14 column CSS grid

Stop using hacks to position elements outside of the container and upgrade to a 14-column CSS Grid.

The problem

Containers are extremely useful. They allow us to limit how wide our content can go. One common requirement is that certain elements need to occupy the space outside of the container too. However, this is often tricky to do in practice. Lets look at an example component:

There is a fixed container in the middle, with a 12 column grid. The green box takes up 6 columns within the grid, but also stretches out all the way to the left of the viewport.

There are a lot of various techniques that I’ve seen people use to achieve this. The majority that I have seen involve “breaking out” of the container with negative margins, or positioning pseudo elements.

The containers in these techniques are usually defined the same way: A centered div with a max-width and some spacing either side (so content doesn’t touch the edge of the viewport).

.container {
    max-width: 1200px;
    margin: 0 auto;
    padding-right: 1rem;
    padding-left: 1rem;
}

The problem is that you’ve now created a container that knows nothing about the remaining space outside of itself. Therefore making you reliant on “hacks” to position elements within that unknown space.

Redefining the container with CSS Grid

So what is the solution? Well, put simply: Our container needs to be full-width.

But that defeats the purpose of the container?!

Well, yes – and I’ll address that shortly. But first, lets talk about what the container in our picture actually is.

A container is made up of:

  • the maximum width of our 12 column grid, along with the spacing of 11 gutters/gaps between the columns
  • plus some additional padding outside of the 12 columns

Step 1 – Setup the 12 column grid

With this in mind, we can start replicate this using CSS Grid. First, lets create the following:

  • a container that will not grow larger than 1260px
  • columns that can shrink to 0px
  • 45px between columns

There’s a little bit of math here, but we’ll break it down as we go.

$container-max-width: 1260px;
$gutter: 45px;

$container-width-without-gutters: $container-max-width - ($gutter * 11); // 1260px - 495px = 765px
$column-max-width: ($container-width-without-gutters / 12); // 765px / 12 columns =  63.75px

.grid-container {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, $column-max-width));
    column-gap: $gutter;
}

This may seem complicated at first, but I’ll walk through each step so you can understand what is going on:

$container-max-width: 1260px;
$gutter: 45px;

Here, we are just defining the maximum width of our container as well as the space needed between columns. Note that we are not going to use the CSS max-width property. You can set these to whatever you need.

$container-width-without-gutters: $container-max-width - ($gutter * 11);
// 1260px - 495px = 765px

As we saw previously, there are 11 gutters/gaps in-between our columns. We also know that we want each of those gutters to be 45px wide. So therefore, we can calculate the total amount of space that gutters will take up within our grid.

In this instance, gutters take up 495px. Which leaves a total of 765px remaining for the columns.

$column-max-width: ($container-width-without-gutters / 12);
// 765px / 12 columns =  63.75px

Split the remaining 765px up evenly between the 12 columns. This gives us the maximum width a column can grow to.

grid-template-columns: repeat(12, minmax(0, $column-max-width));

The only thing left to do is create 12 columns in CSS Grid that can grow up to 63.75px. minmax() here tells the browser that each column can be a minimum of 0px and a maximum of 63.75px.

If we load this up in a browser, we see that we have a 12 column grid that is 1260px wide:

If you want to have a look, here’s a CodePen (note I scaled this down by 2/3 so it’s easier to see what happens on smaller devices)

See the Pen
LYGdOyJ
by Adam Tomat (@adam-tomat)
on CodePen.

Step 2 – Centering the grid. Upgrading to 14 columns

So far, so good. However our 12 column grid is not centered. Also, if you look carefully you can see that the browser has added an 13th column, which fills up any remaining space.

This is called an “implicit column”, and the browser automatically generates these. You can also position elements on them too, like any other column.

Rather than relying on this implicit behaviour, lets explicitly tell the browser that we want:

  • 1 column to the left of the grid that takes up half the available space
  • 1 column to the right of the grid that takes up half the available space

To do this, we can modify our grid-template-columns property by adding a column before and after our 12 column grid:

grid-template-columns: 1fr repeat(12, minmax(0, $column-max-width)) 1fr;

We are using the unit: 1fr (which means 1 fraction) and uses the available space to work out how wide it should be.

Because we have used 1fr on both our outer columns, the browser must give them the same width. It will do this by dividing the available space by 2.

You can see this in action here:

See the Pen
CSS Grid – Step 2
by Adam Tomat (@adam-tomat)
on CodePen.

Step 3 – Positioning elements within the 14 column grid

Fantastic, we have a 14 column grid now! But how do we position elements on it?

Lets go back to our original component and see how we can position that on our new grid. For reference, here it is again:

For this, create a div inside the grid-container and tell it to start at column 1 (our outer left column) and end at column 8.

.box {
  grid-column: 1 / 8;
}

The confusing thing here is that “column 8” is actually referring to the columns in our CSS grid and not our 12 column grid.

The addition of our 2 extra columns has given us a horrible offset when referring to columns.

We can solve this by giving our columns a name. You can call these anything you like – this is just what I use.

Our grid-template-columns property can be updated like so:

grid-template-columns: [outer-left] 1fr [grid-start] repeat(12, [col-start] minmax(0, $column-max-width)) [grid-end] 1fr [outer-right];

Now, we can refactor our .box class to be more expressive and easier to read.

.box {
  grid-column: outer-left / col-start-7;
}

See it in action here:

See the Pen
CSS Grid – Step 4
by Adam Tomat (@adam-tomat)
on CodePen.

Step 4 – Container padding

The last piece of the puzzle is making sure we have the correct padding either side of our 12 column grid. This ensures our content doesn’t touch the edge of the viewport. By default, this amount will be 1 gutter width (45px in our example).

If you want to use a different value, you’ll need to do one of two things depending on whether you want more or less spacing than 1 gutter.

For both of these situations lets assume our viewport is 1000px wide, causing the two outer columns (outer-left and outer-right) to shrink to 0px. The CSS grid will look like this, where the pink sections are the 45px gutters.

More spacing

To increase the spacing, all you need to do is set a minimum width that the outer columns can be.

If we want our outer spacing to be 60px rather than 45px, then we have to increase the spacing by 15px.

Rather than [outer-left] 1fr, we can utilise minmax() again to set the minimum width like so: [outer-left] minmax(15px, 1fr). Putting it all together, looks like this:

grid-template-columns: [outer-left] minmax(15px, 1fr) [grid-left] repeat(12, [col-start] minmax(0, $column-max-width)) [grid-right] minmax(15px, 1fr) [outer-right];

The grid would then look like this, where the green areas are the outer-left and outer-right columns:

Less spacing

This one is a little trickier. Currently, the spacing is 45px. Lets look at what needs to happen in order to have 15px spacing instead. Therefore reducing the spacing by 30px.

With CSS Grid, it is not possible to change the gutter width for a particular column. And we can’t use the outer-columns because they cannot have a negative width.

Therefore, the only way to solve this is to artificially increase the width of our grid-container by using negative margins. This will move part of the outer gutters off-screen so they are no longer visible.

As you can see in the image above, our grid now extends past the viewport – hiding the excess gutter we don’t want. They are still there, they are just not visible.

You will also need a div wrapping your grid-container to crop (overflow: hidden) the bits of the gutter that are now off-screen, otherwise the browser will allow you to scroll.

The CSS would look like this:

.container-wrapper {
    overflow: hidden;
}

.grid-container {
    margin-right: -30px;
    margin-left: -30px;
    // ...etc
}

See this in action on the CodePen below:

See the Pen
CSS Grid – Step 5
by Adam Tomat (@adam-tomat)
on CodePen.

Wrapping up

Using a 14-column grid is a great technique to have in your toolbelt when you need to do more complex layouts, especially considering how well supported CSS Grid is now. We’ve used this approach on a number of sites now and not only does it work really well, the named columns makes a breeze write and also importantly to understand & change later (or by somebody else).

Give it a go and let me know how you get on. Any questions, feel free to @ me on Twitter: https://twitter.com/adamtomat