Skip to content

Full Bleed Layouts

A common blog layout involves a single, centered column of text:

A blog layout with a single large column

In Module 1, we learned about a max-width wrapper utility that can help us with this. We can even combine it with the “ch” unit from Module 6, for a pretty solid approach:

Code Playground

Result

Enable “Tab” for indentation

(In a real setting, we'd want to use 50-75ch, but I've chosen a smaller value in this case because the RESULT tab is so narrow!)

Using a max-width wrapper is a solid approach, but it does lock us in; every in-flow child will be constrained by that container.

But what if we wanted to allow certain children to "break free", and fill the entire window width?

A blog layout with a single large column

Having an element stretch from edge to edge is known as a "full-bleed" element, a term borrowed from the publishing world when magazine ads would be printed right to the edge of the page.

At first blush, this seems like an impossible problem. If our child is in-flow, it will be bound by its containing block (in this case, .max-width-wrapper). And if we take it out of flow, it will break our layout / cause overlaps.

Fortunately, CSS Grid offers a very clever solution to this problem.

Here's an MVP of the solution. Take a moment and see if you can figure out how it works:

Code Playground

Result

Enable “Tab” for indentation

There's a lot going on here, so let's unpack it!

Grid construction

We've created a grid with 3 explicit columns.

  • Our left column takes up 1fr
  • Our center column, where our content will go, takes up min(30ch, 100%)
  • Our right column takes up 1fr

As we saw in an earlier lesson, the ch unit is equal to the width of the 0 character, in the current font. Let's assume that in the current situation, our 0 character is 15px wide. This means that our 30ch value translates to 450px.

450px is too wide to fit on many mobile displays. That's why we have that min() function. It clamps this value so that it never grows above 100% of the available space. On a 375px-wide phone, our center column will be 375px wide, not 450px.

Our two side columns will share whatever space remains. Like auto margins, this is a clever way to make sure the middle column is centered.

Let's imagine that our window is 750px wide. Our center column eats up 450px of space, so there's 200px left over. Because both side columns consume 1fr, they each take 100px, pushing the middle column into the center of the screen.

Here's a visualization showing how this works:

1fr
30ch
1fr

Column assignments

As we start adding children to this grid, they'll be assigned into the first available cell. This doesn't work for us: we want all of our content to be assigned to that middle column by default!

That's where this CSS comes in:

.wrapper > * {
grid-column: 2;
}

The asterisk (*) is a wildcard: it matches everything. This means that every child we pop into this grid will be lined up inside the 2nd column.

Every new child will create its own implicit row, and occupy the center column:

<h1>
<p>
<p>
<p>

Full-bleed children

Finally, we need a way to "opt in" to the full-bleed behaviour!

That's where this fella comes in:

.full-bleed {
grid-column: 1 / -1;
}

Any child that applies the .full-bleed class will stretch from the first column line to the last column line.

Here's how this works by default:

<h1>
<p>
<img class="full-bleed">
<p>

This can lead to some very tall images, on very wide screens, so I like to combine it with a fixed height and object-fit:

.meerkat {
display: block;
width: 100%;
height: 300px;
object-fit: cover;
}

For more information on object-fit, check out the lesson on Fit and Position.

Adding gutters

You may have noticed that when the window is smaller, things feel very cramped:

Screenshot from a smaller window with no padding

We haven't added any gap or padding to this grid, and so it makes sense that the contents would run right up to the edge.

If we add padding to the grid itself, it will give us some breathing room, but it also stops the full-bleed elements from truly being full-bleed:

.wrapper {
display: grid;
grid-template-columns:
1fr
min(30ch, 100%)
1fr;
padding-left: 16px;
padding-right: 16px;
}
Screenshot from a smaller window with padding

Notice that our meerkat photo isn't actually full-bleed anymore. This is actually kind of a tricky problem: how do we give standard children a bit of breathing room without affecting .full-bleed ones?

We can have our cake and eat it too by using negative margin on the full-bleed children:

Code Playground

Result

Enable “Tab” for indentation

Our container has 16px of padding, but our full-bleed children will undo that, using the negative margin trick we saw way back in Module 1!

The only problem with this approach is that there's an implicit dependency between the parent's padding and the child's margin. Later, we may wish to tweak the parent's padding, and forget to update the margin, breaking the layout.

We can address this with CSS variables:

.wrapper {
--breathing-room: 16px;
display: grid;
grid-template-columns:
1fr
min(30ch, 100%)
1fr;
padding-left: var(--breathing-room);
padding-right: var(--breathing-room);
}
.wrapper > * {
grid-column: 2;
}
.full-bleed {
grid-column: 1 / -1;
margin-left: calc(
var(--breathing-room) * -1
);
margin-right: calc(
var(--breathing-room) * -1
);
}

With this variable, there's no way to tweak the padding without also updating the margin.