Kilian Valkhof

Building tools that make developers awesome.

Full bleed layout using simple CSS

CSS & HTML, 6 October 2020, 5 minute read

There are many different ways to achieve the same layout in CSS and HTML. Some are now frowned upon, like tables or floats, and others tend to overlap somewhat, but have a clear specific purpose, like Flexbox and Grid. But CSS has another layout engine, and it’s one that has been part of CSS since the beginning: Flow layout. And it can do a lot.

When I build websites, I try to stick to the principle of least power. This states that you should use the least powerful language that does the job. In other words, don’t go straight for the more complex language (like JS) if a simpler language (like CSS or HTML) can also do the same thing. The simpler language will be more maintainable, more robust and easier to understand.

I try to apply the same to the languages themselves: I try to use the least powerful feature of the language to get the job done.

Josh just posted a wonderful article titled Full-Bleed Layout Using CSS Grid. In it, he shows how to use CSS Grid to create a “full bleed” effect. Full bleed is a term from print, which means that something goes to right to the edge.

In the article Josh uses it to have textual content centered with a readable line length while having an image “break out” and span the width of the page.

With CSS Grid the page is divided into three columns. For text, the two outer columns are empty and the center column is used for text.

Centered content

Images use all three columns and so span the width of the page.

Image content

It’s a clever solution and it works well. And if it works for you that’s great. But it also uses a relatively complex part of CSS (grid layouts) with a considerable amount of CSS.

A simpler solution

The same effect can be done with a single CSS selector and regular Flow layout.

It’s significantly more simple than building up a grid layout, and if I can use a simpler feature of a language to achieve the same effect, I’ll choose that. For example, I’m using it on this page for the code examples right now.

Lets go back to what the linked article stated:

It’s relatively easy to constrain all children, but CSS doesn’t really have a mechanism to selectively constrain some children.

Fortunately, CSS has something that does exactly that: the :not() pseudo-class (fair warning, article from 2008).

So how can we use the not selector to get the same effect? We go back to the original problem statement:

Content should be constrained, except content that should fill the available width.

If we want most content to be constrained and center aligned, there’s CSS for that:

body > * { 
  max-width: 40rem;
  margin: auto;

The combination of max-width and margin: auto let the browser know to equally divide the available margin between the left and right side of the element.

By default an element is 100% wide and there is no space left, so we need to constrain that in some way. We could use a fixed width but then it wouldn’t be responsive. So instead we can use a max-width. That means the content will fill the available width until it’s, in our case, 40rem wide, and then remain centered on the page no matter how wide the page becomes.

Side note: An earlier version of this article added position: relative as well. This is not strictly needed, but I usually add it to all block elements because it makes flow layout work more like you expect it to. I then remove it as needed to get different offsets.

Everything is now centered, but we want our images to break out and be full bleed. So we exclude them from the selector we just made using the :not() pseudo-class:

body > *:not(img) { 
  max-width: 40rem;
  margin: auto;

All that’s left is to add a max-width: 100% to the image element and there you go, a full bleed layout using simple CSS:

See the Pen
Full Bleed Layout using simple CSS
by Kilian Valkhof (@Kilian)
on CodePen.

Getting slightly more complex

The above example is kept simple on purpose. What if you want full bleed images and videos? In that case you can add more :not() pseudo-classes, as many as you need.

body > *:not(img):not(video) { 
  max-width: 40rem;
  margin: auto;

How about the situation where you want the image to be full-bleed, except on really wide monitors? We can use the same trick as we did for the main content except this time, using a wider max-width.

At this point, we can also check to see if there is an opportunity to refactor. Since we want most of the same properties for images as well, we can split the selectors and use the cascade to overwrite the max-width value:

body > * { 
  margin: auto;
  max-width: 40rem;

body img {
  max-width: 1920px;
  display: block;

We changed the code so that the common elements, the max-width and margin are applied to all children. Then we add a second selector, body img, to overwrite the max-width. For completeness we’ll add a display: block because margin: auto only works on block elements, and images are inline elements by default. Usually in this type of layout, you’ll already have done that elsewhere.

As you see we don’t strictly need the :not(), we can rewrite the initial example with two selectors as well. The reason I like the :not() pseudo-class in the example above is that it indicates that images are the exception to whatever we’re styling here, something that’s not immediately apparent in this latest example.

The principle of least power

CSS is very powerful, and I’m convinced we’re just scratching the surface of the type of layouts that Grid and Flexbox will let us create. I’m excited about that. But let’s not forget that CSS also has a simple and powerful layout system with amazing browser support, and use that when it can do what we try to achieve.

Polypane browser for responsive web development and design Hi, I'm Kilian. I make Polypane, the browser for responsive web development and design. If you're reading this site, that's probably interesting to you. Try it out!