Container Query Units

11 June 2023 - 8:23pm

There's been a lot of interesting additions to CSS lately. We've got layers, to help us handle specificity hell. We've got container queries, so we can make widgets responsive. We're even adding native support for masonry-style layouts, which up until now have been all but impossible without using JavaScript.

One of the interesting additions to the container queries spec is support for container query units, which are relative to the size of the container:

  • cqw: 1% of the container's width
  • cqh: 1% of the container's height
  • cqi: 1% of the container's inline size (the same as cqw, unless the container's parent layout flows vertically)
  • cqb: 1% of the container's block size (the same as cqh, unless the container's parent layout flows vertically)
  • cqmin: The smallest of cqw and cqh
  • cqmax: The largest of cqw and cqh

You may notice a certain similarity between container query units and viewport units. However, container query units allow us to do something that viewport units can't. Specifically, container query units can give us the exact width of the window minus its scrollbar width.

On mobile devices and MacOS, the scrollbar is a translucent shape that's only visible during scrolling. On Windows, it's always visible and takes up 10-20 pixels of the screen. This wouldn't be a problem, except viewport units ignore the presence of the scrollbar, so if a page has a scrollbar and an element has a width of 100vw, it will end up being wider than the page.

A practical example

A lot of news articles use a central column for the article text, and then include images that break out of either side of the column to take up the entire screen. If viewport units took scrollbars into account, this would be trivial to achieve:

main
{
    margin: auto;
    max-width: 1200px;
}

.big-image
{
    margin-left: calc(-50vw + 50%);
    width: 100vw;
}

Enter container query units. We can achieve the same effect with only a little bit extra boilerplate:

body
{
    container: body / inline-size;
}

main
{
    margin: auto;
    max-width: 1200px;
}

@container body (min-width: 0px)
{
    .big-image
    {
        margin-left: calc(-50cqw + 50%);
        width: 100cqw;
    }
}

And just like that, container query units swoop in to save the day.


As a side note, you may be wondering about dynamic viewport units. Dynamic viewport units consist of:

  • svw/svh: 1% of the width/height of the smallest possible viewport
  • lvw/lvh: 1% of the width/height of the largest possible viewport
  • dvw/dvh: 1% of the width/height of the current viewport

These are designed for mobile devices, where the top/bottom bars of the browser will be hidden while the user scrolls, in order to save screen real-estate for the page content. Annoyingly, desktop scrollbars were not part of the spec, so 100dvw still gives you the width including the scrollbar.