{"slug": "a-friendly-introduction-to-container-queries", "title": "A Friendly Introduction to Container Queries", "summary": "Container queries are a recently introduced CSS feature that allows developers to apply conditional styles based on the size of a specific parent container, rather than the global viewport. Although they have been supported in all major browsers for nearly two years and were the most requested CSS feature, adoption has been slow due to confusion about how they work and the need to explicitly define a container using the `container-type` property. The article explains that while media queries are limited to global properties like viewport width, container queries solve the \"impossible problem\" of creating responsive components that adapt to their local available space.", "body_md": "[Introduction]\n\nFor a very long time, the most-requested CSS feature has been *container queries.* That’s been our holy grail, the biggest missing piece in the CSS toolkit.\n\n**Well, container queries have finally arrived.** They’ve been supported in all major browsers for almost two years. Our prayers have been answered!\n\nWe can now apply conditional CSS based on an element’s *container*, using familiar syntax:\n\n```\n@container (min-width: 40rem) {\n  .some-elem {\n    font-size: 1.5rem;\n  }\n}\n```\n\n**Curiously, though, very few of us have actually been using container queries.** Most of the developers I’ve spoken with have only done a few brief experiments, if they’ve tried them at all. We finally have the tool we’ve been asking for, but we haven’t adopted it.\n\nThere are lots of reasons for this, but I think one of the biggest is that there’s been a lot of confusion around *how they work.* Container queries are not as straightforward as media queries. In order to use them effectively, we need to understand what the constraints are, and how to work within them.\n\nI’ve been using container queries for a few months now, and they really are quite lovely once you have the right mental model. In this blog post, we’ll unpack all of this stuff so that you can start using them in your work!\n\n[Link to this heading](#the-basic-idea-1)The basic idea\n\nSo for the past couple of decades, our main tool for doing *responsive design* has been the media query. Most commonly, we use the width of the viewport to conditionally apply some CSS:\n\n```\n@media (min-width: 40rem) {\n  .mobile-only {\n    display: none;\n  }\n}\n```\n\nMedia queries are great, but they’re only concerned with *global* properties, things like the viewport dimensions or the operating system’s color theme. Sometimes, we want to apply CSS conditionally based on something *local*, like the size of the element’s container.\n\nFor example, suppose we have a `ProfileCard`\n\ncomponent, to display critical info about a user’s profile:\n\nIn this particular circumstance, each `ProfileCard`\n\nis pretty narrow, and so the information stacks vertically in 1 tall column.\n\nIn other circumstances, though, we might have a bit more breathing room. Wouldn’t it be cool if our ProfileCard could automatically shift between layouts, depending on the available space?\n\nMaybe something like this:\n\nIn *some* cases, we can use media queries for this, if our `ProfileCard`\n\nscales with the size of the viewport… But this won’t always be the case.\n\nFor example, maybe we’re arranging these cards in a flex grid like this:\n\nWith dynamic layouts like this, each `ProfileCard`\n\nwill use whichever layout makes the most sense given the amount of space available. It has nothing to do with the size of the viewport!\n\nClearly, media queries aren’t the right tool for this job. Instead, we can use *container queries* to solve this problem. Here’s what it looks like:\n\n```\n.child-wrapper {\n  container-type: inline-size;\n}\n\n.child {\n  /* Narrow layout stuff here */\n\n  @container (min-width: 15rem) {\n    /* Wide layout stuff here */\n  }\n}\n```\n\nPretty cool, right? I'm using native CSS nesting to place the `@container`\n\nat-rule right inside the `.child`\n\nblock so that all of the CSS declarations for this element are in the same chunk of CSS.\n\n**But wait, what’s the deal with .child-wrapper?** What is that\n\n`container-type`\n\nproperty doing??Well, this is where things get a bit tricky. In order to use a container query, we first need to explicitly define its container. This can have some unintended consequences.\n\n**It’s worth spending a few minutes digging into this.** Understanding this core mechanism will save us *hours* of frustration down the line. Let’s talk about the “impossible problem” with container queries.\n\n[Link to this heading](#solving-an-impossible-problem-2)Solving an impossible problem\n\nFor something like 20 years now, ever since “responsive design” became a thing, developers have been asking for container queries. So why are we only being introduced now??\n\nWell, for something like 20 years, the CSS Working Group has been saying the same thing: *It’s impossible to implement container queries. It can’t be done.*\n\n**This’ll be much easier to understand with an example.** Consider this scenario:\n\nCode Playground\n\nResult\n\nIf you’re not familiar with the `fit-content`\n\nkeyword, it’s a dynamic value that grows/shrinks based on the element’s content. If you add/remove some words to the paragraph, you’ll notice the paragraph change size:\n\n**Hello world!**\n\nNow, let’s suppose we want to bump up the `font-size`\n\nof that bold text, depending on the size of its container. We can imagine doing something like this:\n\n```\np {\n  width: fit-content;\n\n  @container (max-width: 10rem) {\n    strong {\n      font-size: 3rem;\n    }\n  }\n}\n```\n\nThis seems to make sense… When our parent `<p>`\n\ntag is 10rem or smaller, we apply `font-size: 3rem`\n\nto the `<strong>`\n\ntag within.\n\n**But let’s really think about this.** When we change an element’s `font-size`\n\n, it doesn’t just affect the height of the characters. It also affects the element’s *width*:\n\n**Hello World!**\n\nWhen our container is 10rem or smaller, we apply styles that cause the container to grow *beyond* 10rem. The CSS that we apply conditionally causes the condition to no longer be met!\n\n**This next demo shows what would happen if this sort of thing were allowed.** Reduce the number of characters until the container is less than 10rem, and notice what happens:\n\nThis is mindbending stuff, and it took me a minute to really understand the problem here.\n\nWhen our `<section>`\n\nis less than 10rem wide, our condition is met, and so we apply some CSS, bumping up the font size. But this causes our `<p>`\n\ntag to expand, which causes the parent `<section>`\n\nto grow beyond the `10rem`\n\nthreshold! The CSS we write *inside* a container query can affect the container itself, leading to these infinite loops of flickering UI.\n\nThis is the core problem that the CSS Working Group said was unsolveable. This is why we haven’t had container queries until now.\n\nWe don’t run into this problem with *media* queries because their conditions are based on immutable global states. CSS does not give us the power to change things like the width of the viewport or the user’s motion preferences. So there’s no way for us to invalidate a media query from within it.\n\nI’m using the `fit-content`\n\nkeyword to demonstrate the issue here, but the problem is much more broad than this one niche property. Lots of things in CSS work this way, with parents dynamically responding to their children.\n\nThe solution to this unsolveable problem appeared suddenly, with the introduction of a completely unrelated API.\n\n[Link to this heading](#the-containment-api-3)The Containment API\n\nThe *Containment* API, released a few years ago, allows us to specify that certain slices of the DOM are self-contained, and won’t leak out and affect other parts of the DOM.\n\nI don’t want to go on too much of a tangent here, but here’s a quick demonstration that shows how this API works:\n\nBy default, our red box will grow and shrink to contain its children. This is exactly the sort of dynamic behaviour that causes problems for container queries.\n\n**By setting contain: size on the parent, we sever this connection.** As a result, the height of the container no longer depends on its content. If we don’t specify an explicit\n\n`height`\n\n, the container will collapse down to 0px (plus padding).The Containment API was designed with performance optimizations in mind. CSS is a very dynamic language, and this means the browser often has to do *a lot* of work when things change. For example: when we tweak the height of the axolotl image, it affects not only the elements within that demo, but *everything that follows in this article*. Paragraphs like this one gets shifted up and down on every size change, causing a layout recalculation and a repaint.\n\nAnd so, if we *know* that an element is self-contained and won’t affect anything else, we can use the `contain`\n\nproperty to let the browser know that it can skip certain calculations. A helpful analogy for React devs: it’s a bit like `React.memo()`\n\n. We can use `contain`\n\nto opt out of recalculations that we know are unnecessary.\n\nNow, truthfully, I haven’t found myself using `contain`\n\non a regular basis. Modern browsers are already heavily optimized and will skip calculations that are obviously unnecessary. I get the impression that `contain`\n\nis mostly intended for edge-cases, or for situations where every last drop of performance is critical.\n\n**But this API has provided the final foundational piece for container queries!** This is how we solve the impossible problem. This API gives us the ability to “short-circuit” the infinite loop by specifying that a parent *shouldn’t* respond dynamically to its content.\n\n[Link to this heading](#our-first-container-query-4)Our first container query\n\nWith all of that context in mind, let’s write a “hello world” container query:\n\nCode Playground\n\nResult\n\nFirst, we declare that the `<section>`\n\nelement is a container. This will allow any of its descendants to use it as a measuring stick, to apply CSS when certain conditions are met.\n\nNext, we create a container query, selecting the `<p>`\n\nwithin our container and tweaking its cosmetic styles when the container is 12rem wide or less. When that condition is met, the CSS within that block will be applied, and the text will become bold and red.\n\nIf you’re viewing this on a device with a large screen, you can see this for yourself: resize the **RESULT** pane by clicking and dragging the divider, or focusing it and using the left/right arrow keys.\n\n**There’s a problem with this implementation, though.** It becomes apparent when we give our container some cosmetic styles:\n\nCode Playground\n\nResult\n\nLike we saw with the axolotl example, the parent element is no longer responding dynamically to its children. Instead of growing to fit the paragraphs within, it collapses down to nothing; the only reason we can see the background color at all is because this element happens to have some padding!\n\nWhen we set `container-type: size`\n\n, we tell the browser that this element’s layout doesn’t depend on its children. This prevents the infinite loop we saw earlier, but it also breaks one of our core assumptions about how CSS works!\n\nWe don’t often think about it, but there’s a fundamental difference between width and height on the web:\n\n- When it comes to width, elements tend to expand, filling the space provided by the\n*parent.* - When it comes to height, elements tend to shrinkwrap around their\n*children.*\n\nConsider an empty `<div>`\n\nwith no CSS applied to it. It will be 0px tall, **but it won’t be 0px wide.** It’ll grow to fill the entire horizontal space, regardless of whether it has any content or not.\n\nWhen we set `container-type: size`\n\n, we tell CSS to ignore its content, which means it reverts to the default behaviour of collapsing down to zero!\n\nFortunately, there’s another value we can use for the `container-type`\n\nproperty, `inline-size`\n\n:\n\nCode Playground\n\nResult\n\nThe term `inline-size`\n\nhere refers to the *inline dimension,* which is typically width.\n\nEssentially what we’re saying here is that the *width* of the element does not depend on its content. As a result, it can be used as a measuring stick by its descendants. The element’s *height*, by contrast, retains its default behaviour of growing/shrinking based on its content.\n\n**The golden rule with container queries is that we can’t change what we measure.** `container-type: inline-size`\n\nlets us use `min-width`\n\n/`max-width`\n\nconditions in our container queries, but not `min-height`\n\n/`max-height`\n\n.\n\n(Credit to [Miriam Suzanne(opens in new tab)](https://www.miriamsuzanne.com/) for coining this golden rule. Miriam is also the person who solved the impossible problem with container queries, and the main reason we have them today. She’s the best.)\n\n[Link to this heading](#browser-support-5)Browser support\n\nContainer queries are supported in all 4 major browsers, starting from:\n\n- Safari 16, introduced in September 2022\n- Chrome/Edge 105, introduced in August 2022\n- Firefox 110, introduced in February 2023\n\nAs I write this in November 2024, container queries are at ~93%. Here's a live embed with up-to-date values:\n\nI should also note that my examples in this blog post use [CSS Nesting(opens in new tab)](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting). This recently became a native CSS feature, though it’s been a standard feature in just about every CSS preprocessor / framework out there. If you’re not using any CSS tooling, you should also check out the [native CSS nesting browser support(opens in new tab)](https://caniuse.com/css-nesting).\n\n[Link to this heading](#a-new-responsive-world-6)A new responsive world\n\nAs I said in the introduction, container queries have been surprisingly underutilized. Very few of the devs I’ve spoken with have actually started using them regularly in their work.\n\nOne of the core reasons for this is that container queries are complicated, and my goal with this blog post is to help clarify them. But I think there’s another big reason why container queries haven’t been adopted, something we haven’t talked about yet.\n\nAs developers, we implement the mockups that designers prepare for us. This has always been a back-and-forth, a negotiation between what the designers want and what the developers can implement. And for almost 20 years now, we’ve made it clear that “responsive design” was limited to the viewport.\n\nI don’t think most designers are even *aware* that they have this exciting new capability. *It’s our job to share these developments with them,* so that they can use them in their designs!\n\nFor the projects I work on (this blog and my course platform), I’m both the developer *and* the designer. I have no excuse. When I [redesigned my blog](/blog/how-i-built-my-blog-v2/) over the summer, I made a conscious effort to use container queries. **And once I started thinking in terms of containers, I kept seeing opportunities to use them!**\n\nI recently published another blog post about container queries, which is a sort of spiritual successor to this one. It goes beyond the fundamentals, and shares some of the exciting new design patterns that are made possible by container queries. You can check it out here:\n\n### Last updated on\n\nNovember 4th, 2024", "url": "https://wpnews.pro/news/a-friendly-introduction-to-container-queries", "canonical_source": "https://www.joshwcomeau.com/css/container-queries-introduction/", "published_at": "2024-11-04 13:15:00+00:00", "updated_at": "2026-05-22 14:57:24.600776+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["CSS"], "alternates": {"html": "https://wpnews.pro/news/a-friendly-introduction-to-container-queries", "markdown": "https://wpnews.pro/news/a-friendly-introduction-to-container-queries.md", "text": "https://wpnews.pro/news/a-friendly-introduction-to-container-queries.txt", "jsonld": "https://wpnews.pro/news/a-friendly-introduction-to-container-queries.jsonld"}}