Skip to contentSkip to footer

If you want to be kept up to date with new articles, CSS resources and tools, join our newsletter.

Container queries are an (hopefully) upcoming feature in CSS that will let you create responsive designs that respond to the dimensions of their container (like a div or other HTML element) rather than the dimensions of the entire page, which we currently do with media queries.

This is super helpful for component-based design, since you no longer have to create different variants of a component when you need them to display slightly differently depending on where they're shown.

Unfortunately, container queries don't exist yet.

Recently this became an issue for me when Facebook updated their rich previews on desktop to be shown at three discrete widths depending on the viewport width.

Polypane as shared on Facebook at three discrete widths

That meant I had to update the previews in the Polypane side panel to show at only those three widths. The side panel can be sized independent of the viewport though, so media queries were not an option. Because they used discrete widths, I also couldn't use auto sizing to let the preview grow along with the width of the side panel. I needed container queries.

Note: this has since been updated/reverted (or I was in some A/B testing group) but the technique I came up with was useful enough to still write an article about.

With CSS Grid and some trickery I managed to implement this in a way that looks very close to container queries.

Just to note: real container queries would let you do significantly more than having three discrete widths so this is in no way a full replacement. But it's useful enough that I think it's a technique to know about.

Defining the problem

The problem we're trying to solve is this: depending on the width of the viewport, the rich previews on Facebook are either 500, 590 or 680 pixels wide. In Polypane, we want to show the largest version that fits in the sidebar.

We can't use the viewport width, since the width of the sidebar is independent of it. And we can't use a fluid width combined with a max-width, since we need it to snap to those three sizes so it acts just like Facebook.

If we look at those three widths we see that from 500 pixels, the width increases by 90 pixels each time. This will come of use later.

Getting the preview exactly right might feel pedantic, but I want the Social media previews in Polypane to be pixel perfect as well as character perfect, so you can be sure that what you see in Polypane is what people see on social media sites.


This is something not even the official validators of Facebook, Twitter and LinkedIn do. In fact, they all use outdated designs that no longer match with the actual sites, making Polypane the only place that matches with reality. That takes a lot of work, but it's totally worth it.

CSS Grid

Admittedly I haven't done a lot with CSS Grid yet so I am by no means a pro. I usually default to Flexbox as it fits most of the things I need to design. Here's how I decide whether to use CSS Grid or Flexbox:

  • Do I have a bunch of elements that I need to lay out? Then I use Flexbox
  • Do I have a bunch of areas that I need to put elements in? Then I use CSS Grid.

And in this case, the area should be leading, which led me (along with some help from the Fronteers Slack) to see if I could get CSS Grid for my solution. In CSS Grid you can define a set of areas in two dimensions (though we only need one dimension for this example) so that's what we'll do.

Setup

We begin with setting up a wrapper for our Facebook preview to live in:

<div class="wrapper">
  <div class="preview">Facebook Preview</div>
</div>

And some CSS that will set up the grid in the wrapper, and then position the preview inside it:

.wrapper {
  display: grid;
  max-width: 680px;
  grid-template-columns: 500px repeat(auto-fill, 90px);
}

.preview {
  grid-column: 1 / -1;
}

What we've done here is set up a CSS grid that's at most 680 pixels wide (the widest our preview should get), has one column that's 500px width (the smallest our preview should get) and then a "repeat()" function that adds columns of 90 pixels wide as long as they fit, which is what the auto-fill function does.

Then, for our preview, we set grid-column to 1 / -1. What this means is this element goes from the first grid line from the start (1), to the first grid line from the end (-1). This essentially means it will always occupy all grid areas regardless of how many grid areas there are.

Here's a live, resizable preview you can try and see what happens. The white dotted lines indicate the borders of the grid areas (drag the bottom right corner):

Preview

If you look closely, you can see that it doesn't actually work: while the Facebook preview snaps to 680px and 590px wide, it doesn't go to our 500px width. This is because repeat() always shows at least one column. I initially thought this would be at least zero.

So how do we fix that?

Getting it just right

Since we now know there's always at least one column of 90 pixels, and the minimum size we want is 500px, we need the first column in our CSS Grid to be 500 - 90 = 410 pixels wide. Let's try that:

.wrapper {
  display: grid;
  max-width: 680px;
  grid-template-columns: 410px repeat(auto-fill, 90px);
}

And a new live preview:

Preview

The preview area now snaps to 680, 590 and 500 pixels wide. We've created a container query-like experience where depending on the available total width we snap to a set of fixed sizes.

As noted earlier in the article, Facebook has since reverted/updated to a fluid column that scales along with the viewport. This technique can still be useful to achieve certain effects. Maybe you want things to align on a pixel basis, or have a grid system but want to keep fixed widths within that grid.

If you end up using this, let me know on Twitter!

Build your next project with Polypane

  • Use all features on all plans
  • On Mac, Window and Linux
  • 14-day free trial – no credit card needed
Try for free
Polypane screenshot