Misleading Container Queries



This content originally appeared on dbushell.com and was authored by dbushell.com

CSS container queries are spectacular!

And I keep being mislead by the same mistake. It’s starting to irritate me! In this blog post I document a bad code pattern to avoid.

The Mistake

Consider this example with a <div> and two <p> elements inside:

<div class="box">
  <p class="query-less">Less than 600px</p>
  <p class="query-greater">Greater or equal to 600px</p>
</div>

With the CSS below I’ve made box a named container. The two child elements are hidden by default.

.box {
  container: box / inline-size;
}

[class^='query'] {
  display: none;
}

A container query can show the first paragraph using “less than”:

@container box (inline-size < 600px) {
  .query-less {
    display: block;
  }
}

And the second paragraph is shown with a “greater than” query:

@container box (inline-size >= 600px) {
  .query-greater {
    display: block;
  }
}

Simple, right? The box content changes depending on its inline size. Basic container query stuff and perfect for responsive components.

Now consider the following container query change:

.box {
  container: box / inline-size;
}

[class^='query'] {
  display: none;
}

@container (inline-size >= 600px) {
  .box {
    display: none;
    & .query-greater {
      display: block;
    }
  }
}

Note that I’m using standard CSS nesting and I’ve removed the box name from the @container query.

Assuming the query matches, how are styles applied?

  1. Is .box now hidden?
  2. Is .query-greater now visible?

The answer is “no” the box remains visible, not hidden, and “yes” the second paragraph is now visible.

Does that look weird to you? It looks weird to me! The parent style is not applied but the nested style is applied. When I think about it logically it makes sense but it’s not obvious to see. The presentation is deceiving. With nested CSS one would assume all previous selectors match — and technically they do — but not all styles are applied here.

There are a few lessons to remember:

  • The container itself can’t be styled inside a @container query
  • If the container name is omitted the closest parent container is used

In my example code, because .box has no parent container display: none is never applied. The .box .query-greater selector targets a child of .box so this is applied based on the box container size. When “un-nested” the selectors and their styles are entirely independent.

CodePen Example

I’ve published a full CodePen demo to show my bad code pattern.

In Practice

I keep falling into the trap of the failed pattern:

@container (inline-size >= 600px) {
  .card:has(img) {
    display: grid;
    grid-template-columns: 200px 1fr;

    /* Card children styles */
    & > img {
      border-radius: 15px;
    }
  }
}

I have a .card component with an optional image. My intention is to responsively adapt the component as a two-column grid if an image exists. This fails because card is the container and so grid styles won’t apply. However, the card children are styled, which makes me very confused until I realise I’ve made the same mistake, again.

So here I am blogging my mistake in the hopes of committing it to memory.

In such cases I should always specify the container name in the query — i.e. @container box — because that’s always my intended target. This would help visually highlight the mistake of trying to style the container.

Ahmad Shadeed has a guide including more “Common pitfalls for container queries”. Well worth reading the whole guide.


This content originally appeared on dbushell.com and was authored by dbushell.com