{"id":305,"date":"2024-06-14T11:00:00","date_gmt":"2024-06-14T11:00:00","guid":{"rendered":"https:\/\/blissfuldebt.com\/?p=305"},"modified":"2025-03-06T17:24:39","modified_gmt":"2025-03-06T17:24:39","slug":"what-are-css-container-style-queries-good-for","status":"publish","type":"post","link":"https:\/\/blissfuldebt.com\/index.php\/2024\/06\/14\/what-are-css-container-style-queries-good-for\/","title":{"rendered":"What Are CSS Container Style Queries Good For?"},"content":{"rendered":"

What Are CSS Container Style Queries Good For?<\/title><\/p>\n<article>\n<header>\n<h1>What Are CSS Container Style Queries Good For?<\/h1>\n<address>Juan Diego Rodr\u00edguez<\/address>\n<p> 2024-06-14T11:00:00+00:00<br \/>\n 2025-03-06T17:04:34+00:00<br \/>\n <\/header>\n<p>We\u2019ve relied on media queries for a long time in the responsive world of CSS but they have their share of limitations and have shifted focus more towards accessibility than responsiveness alone. This is where <a href=\"https:\/\/www.smashingmagazine.com\/2021\/05\/complete-guide-css-container-queries\/\">CSS Container Queries<\/a> come in. They completely change how we approach responsiveness, shifting the paradigm away from a viewport-based mentality to one that is more considerate of a component\u2019s context, such as its <code>size<\/code> or <code>inline-size<\/code>.<\/p>\n<p>Querying elements by their dimensions is one of the two things that CSS Container Queries can do, and, in fact, we call these <strong>container size queries<\/strong> to help distinguish them from their ability to query against a component\u2019s current styles. We call these <strong>container style queries<\/strong>.<\/p>\n<p>Existing container query coverage has been largely focused on container size queries, <a href=\"https:\/\/caniuse.com\/css-container-queries\">which enjoy 90% global browser support<\/a> at the time of this writing. Style queries, on the other hand, <a href=\"https:\/\/caniuse.com\/css-container-queries-style\">are only available behind a feature flag in Chrome 111+ and Safari Technology Preview<\/a>.<\/p>\n<p>The first question that comes to mind is <em>What are these style query things?<\/em> followed immediately by <em>How do they work?<\/em>. There are some nice primers on them that others have written, and they are worth checking out.<\/p>\n<p>But the more interesting question about CSS Container Style Queries might actually be <em>Why we should use them?<\/em> The answer, as always, is nuanced and could simply be <em>it depends<\/em>. But I want to poke at style queries a little more deeply, not at the syntax level, but what exactly they are solving and what sort of use cases we would find ourselves reaching for them in our work if and when they gain browser support.<\/p>\n<h2 id=\"why-container-queries\">Why Container Queries<\/h2>\n<p>Talking purely about responsive design, <a href=\"https:\/\/www.smashingmagazine.com\/2024\/05\/beyond-css-media-queries\/\">media queries have simply fallen short in some aspects<\/a>, but I think the main one is that they are context-agnostic in the sense that they only consider the <strong>viewport size<\/strong> when applying styles without involving the size or dimensions of an element\u2019s parent or the content it contains.<\/p>\n<p>This usually isn\u2019t a problem since we only have a main element that doesn\u2019t share space with others along the x-axis, so we can style our content depending on the viewport\u2019s dimensions. However, if we stuff an element into a smaller parent and maintain the same viewport, the media query doesn\u2019t kick in when the content becomes cramped. This forces us to write and manage an entire set of media queries that target super-specific content breakpoints.<\/p>\n<p>Container queries break this limitation and allow us to query much more than the viewport\u2019s dimensions.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/what-are-css-container-style-queries-good-for\/1-diagram-component-web-browser.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"450\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Diagram of a component in a web browser with examples of how to query its size and the viewport size.\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/what-are-css-container-style-queries-good-for\/1-diagram-component-web-browser.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Figure 1: When media queries look only at the viewport, they overlook important details about the elements in the viewport, most notably, their sizes. (<a href=\"https:\/\/files.smashing.media\/articles\/what-are-css-container-style-queries-good-for\/1-diagram-component-web-browser.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<div data-audience=\"non-subscriber\" data-remove=\"true\" class=\"feature-panel-container\">\n<aside class=\"feature-panel\">\n<div class=\"feature-panel-left-col\">\n<div class=\"feature-panel-description\">\n<p>Meet <strong><a data-instant href=\"https:\/\/www.smashingconf.com\/online-workshops\/\">Smashing Workshops<\/a><\/strong> on <strong>front-end, design & UX<\/strong>, with practical takeaways, live sessions, <strong>video recordings<\/strong> and a friendly Q&A. With Brad Frost, St\u00e9ph Walter and <a href=\"https:\/\/smashingconf.com\/online-workshops\/workshops\">so many others<\/a>.<\/p>\n<p><a data-instant href=\"smashing-workshops\" class=\"btn btn--green btn--large\">Jump to the workshops \u21ac<\/a><\/div>\n<\/div>\n<div class=\"feature-panel-right-col\"><a data-instant href=\"smashing-workshops\" class=\"feature-panel-image-link\"><\/p>\n<div class=\"feature-panel-image\">\n<img decoding=\"async\" loading=\"lazy\" class=\"feature-panel-image-img lazyload\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Feature Panel\" width=\"257\" height=\"355\" data-src=\"\/images\/smashing-cat\/cat-scubadiving-panel.svg\"><\/p>\n<\/div>\n<p><\/a>\n<\/div>\n<\/aside>\n<\/div>\n<h2 id=\"how-container-queries-generally-work\">How Container Queries Generally Work<\/h2>\n<p>Container size queries work similarly to media queries but allow us to apply styles depending on the container\u2019s properties and computed values. In short, they allow us to make style changes based on an element\u2019s computed <code>width<\/code> or <code>height<\/code> regardless of the viewport. This sort of thing was once only possible with JavaScript or the ol\u2019 jQuery, <a href=\"https:\/\/stackoverflow.com\/questions\/52759798\/if-element-size-is-smaller-than-x-then\">as this example shows<\/a>.<\/p>\n<p>As noted earlier, though, container queries can query an element\u2019s styles in addition to its dimensions. In other words, <strong>container style queries<\/strong> can look at and track an element\u2019s properties and apply styles to other elements when those properties meet certain conditions, such as when the element\u2019s <code>background-color<\/code> is set to <code>hsl(0 50% 50%)<\/code>.<\/p>\n<p>That\u2019s what we mean when talking about CSS Container Style Queries. It\u2019s a proposed feature <a href=\"https:\/\/www.w3.org\/TR\/css-contain-3\/#style-container\">defined<\/a> in the same <a href=\"https:\/\/www.w3.org\/TR\/css-contain-3\/\">CSS Containment Module Level 3 specification<\/a> as <a href=\"https:\/\/www.w3.org\/TR\/css-contain-3\/#size-container\">CSS Container Size Queries<\/a> — and one that\u2019s currently unsupported by any major browser — so the difference between style and size queries can get a bit confusing as we\u2019re technically talking about two related features under the same umbrella.<\/p>\n<p>We\u2019d do ourselves a favor to backtrack and first understand what a \u201ccontainer\u201d is in the first place.<\/p>\n<h3 id=\"containers\">Containers<\/h3>\n<p>An element\u2019s container is any ancestor with a <em>containment context<\/em>; it could be the element\u2019s direct parent or perhaps a grandparent or great-grandparent.<\/p>\n<p>A <strong>containment context<\/strong> means that a certain element can be used as a container for querying. Unofficially, you can say there are two types of containment context: <strong>size containment<\/strong> and <strong>style containment<\/strong>.<\/p>\n<p><strong>Size containment<\/strong> means we can query and track an element\u2019s dimensions (i.e., <code>aspect-ratio<\/code>, <code>block-size<\/code>, <code>height<\/code>, <code>inline-size<\/code>, <code>orientation<\/code>, and <code>width<\/code>) with <strong>container size queries<\/strong> as long as it\u2019s registered as a container. Tracking an element\u2019s dimensions requires a little processing in the client. One or two elements are a breeze, but if we had to constantly track the dimensions of all elements — including resizing, scrolling, animations, and so on — it would be a huge performance hit. That\u2019s why no element has size containment by default, and we have to manually register a size query with the <a href=\"https:\/\/css-tricks.com\/almanac\/properties\/c\/container-type\/\">CSS <code>container-type<\/code> property<\/a> when we need it.<\/p>\n<p>On the other hand, <strong>style containment<\/strong> lets us query and track the computed values of a container\u2019s specific properties through <strong>container style queries<\/strong>. As it currently stands, we can only check for custom properties, e.g. <code>--theme: dark<\/code>, but soon we could check for an element\u2019s computed <code>background-color<\/code> and <code>display<\/code> property values. Unlike size containment, we are checking for raw style properties before they are processed by the browser, alleviating performance and allowing all elements to have style containment by default.<\/p>\n<p>Did you catch that? <strong>While size containment is something we manually register on an element, style containment is the default behavior of <em>all<\/em> elements.<\/strong> There\u2019s no need to register a style container because <a href=\"https:\/\/css-tricks.com\/digging-deeper-into-container-style-queries\/\">all elements are style containers by default<\/a>.<\/p>\n<p>And how do we register a containment context? The easiest way is to use the <a href=\"https:\/\/css-tricks.com\/almanac\/properties\/c\/container-type\/\"><code>container-type<\/code><\/a> property. The <code>container-type<\/code> property will give an element a containment context and its three accepted values — <code>normal<\/code>, <code>size<\/code>, and <code>inline-size<\/code> — define which properties we can query from the container.<\/p>\n<pre><code class=\"language-css\">\/* Size containment in the inline direction *\/\n.parent {\n container-type: inline-size;\n}\n<\/code><\/pre>\n<p>This example formally establishes a <em>size<\/em> containment. If we had done nothing at all, the <code>.parent<\/code> element is already a container with a <em>style<\/em> containment.<\/p>\n<h3 id=\"size-containment\">Size Containment<\/h3>\n<p>That last example illustrates size containment based on the element\u2019s <code>inline-size<\/code>, which is a fancy way of saying its <em>width<\/em>. When we talk about normal document flow on the web, <a href=\"https:\/\/www.smashingmagazine.com\/2018\/03\/understanding-logical-properties-values\/\">we\u2019re talking about elements that flow in an inline direction and a block direction that corresponds to width and height, respectively<\/a>, in a horizontal writing mode. If we were to rotate the writing mode so that it is vertical, then \u201cinline\u201d would refer to the height instead and \u201cblock\u201d to the width.<\/p>\n<p>Consider the following HTML:<\/p>\n<pre><code class=\"language-html\"><div class=\"cards-container\">\n <ul class=\"cards\">\n <li class=\"card\"><\/li>\n <\/ul>\n<\/div>\n<\/code><\/pre>\n<p>We could give the <code>.cards-container<\/code> element a containment context in the inline direction, allowing us to make changes to its descendants when its <code>width<\/code> becomes too small to properly display everything in the current layout. We keep the same syntax as in a normal media query but swap <code>@media<\/code> for <code>@container<\/code><\/p>\n<pre><code class=\"language-css\">.cards-container {\n container-type: inline-size;\n }\n\n @container (width < 700px) {\n .cards {\n background-color: red;\n }\n}\n<\/code><\/pre>\n<p>Container syntax works almost the same as media queries, so we can use the <code>and<\/code>, <code>or<\/code>, and <code>not<\/code> operators to chain different queries together to match multiple conditions.<\/p>\n<pre><code class=\"language-css\">@container (width < 700px) or (width > 1200px) {\n .cards {\n background-color: red;\n }\n}\n<\/code><\/pre>\n<p>Elements in a size query look for the closest ancestor with size containment so we can apply changes to elements deeper in the DOM, like the <code>.card<\/code> element in our earlier example. If there is no size containment context, then the <code>@container<\/code> at-rule won\u2019t have any effect.<\/p>\n<pre><code class=\"language-css\">\/* \ud83d\udc4e \n * Apply styles based on the closest container, .cards-container\n *\/\n@container (width < 700px) {\n .card {\n background-color: black;\n }\n}\n<\/code><\/pre>\n<p>Just looking for the closest container is messy, so it\u2019s good practice to name containers using the <code>container-name<\/code> property and then specifying which container we\u2019re tracking in the container query just after the <code>@container<\/code> at-rule.<\/p>\n<pre><code class=\"language-css\">.cards-container {\n container-name: cardsContainer;\n container-type: inline-size;\n}\n\n@container cardsContainer (width < 700px) {\n .card {\n background-color: #000;\n }\n}\n<\/code><\/pre>\n<p>We can use the shorthand <code>container<\/code> property to set the container name and type in a single declaration:<\/p>\n<pre><code class=\"language-css\">.cards-container {\n container: cardsContainer \/ inline-size;\n\n \/* Equivalent to: *\/\n container-name: cardsContainer;\n container-type: inline-size;\n}\n<\/code><\/pre>\n<p>The other <code>container-type<\/code> we can set is <code>size<\/code>, which works exactly like <code>inline-size<\/code> — only the containment context is both the inline <em>and<\/em> block directions. That means we can also query the container\u2019s height sizing in addition to its width sizing.<\/p>\n<pre><code class=\"language-css\">\/* When container is less than 700px wide *\/\n@container (width < 700px) {\n .card {\n background-color: black;\n }\n}\n\n\/* When container is less than 900px tall *\/\n@container (height < 900px) {\n .card {\n background-color: white;\n }\n}\n<\/code><\/pre>\n<p>And it\u2019s worth noting here that if two separate (not chained) container rules match, the most specific selector wins, true to how the CSS Cascade works.<\/p>\n<p>So far, we\u2019ve touched on the concept of CSS Container Queries at its most basic. We define the type of containment we want on an element (we looked specifically at size containment) and then query that container accordingly.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"container-style-queries\">Container Style Queries<\/h2>\n<p>The third value that is accepted by the <code>container-type<\/code> property is <code>normal<\/code>, and it sets style containment on an element. Both <code>inline-size<\/code> and <code>size<\/code> are stable across all major browsers, but <a href=\"https:\/\/caniuse.com\/css-container-queries-style\"><code>normal<\/code> is newer and only has modest support<\/a> at the moment.<\/p>\n<p>I consider <code>normal<\/code> a bit of an oddball because we don\u2019t have to explicitly declare it on an element since all elements are style containers with style containment right out of the box. It\u2019s possible you\u2019ll never write it out yourself or see it in the wild.<\/p>\n<pre><code class=\"language-css\">.parent {\n \/* Unnecessary *\/\n container-type: normal;\n}\n<\/code><\/pre>\n<p>If you do write it or see it, it\u2019s likely to undo size containment declared somewhere else. But even then, it\u2019s possible to reset containment with the global <code>initial<\/code> or <code>revert<\/code> keywords.<\/p>\n<pre><code class=\"language-css\">.parent {\n \/* All of these (re)set style containment *\/\n container-type: normal;\n container-type: initial;\n container-type: revert;\n}\n<\/code><\/pre>\n<p>Let\u2019s look at a simple and somewhat contrived example to get the point across. We can define a custom property in a container, say a <code>--theme<\/code>.<\/p>\n<pre><code class=\"language-css\">.cards-container {\n --theme: dark;\n}\n<\/code><\/pre>\n<p>From here, we can check if the container has that desired property and, if it does, apply styles to its descendant elements. <a href=\"https:\/\/www.oddbird.net\/2023\/07\/05\/contain-root\/\">We can\u2019t directly style the container<\/a> since it could unleash an infinite loop of <em>changing the styles<\/em> and <em>querying the styles<\/em>.<\/p>\n<pre><code class=\"language-css\">.cards-container {\n --theme: dark;\n}\n\n@container style(--theme: dark) {\n .cards {\n background-color: black;\n }\n}\n<\/code><\/pre>\n<p>See that <code>style()<\/code> function? In the future, we may want to check if an element has a <code>max-width: 400px<\/code> through a style query instead of checking if the element\u2019s computed value is bigger than <code>400px<\/code> in a size query. That\u2019s why we use the <code>style()<\/code> wrapper to differentiate style queries from size queries.<\/p>\n<pre><code class=\"language-css\">\/* Size query *\/\n@container (width > 60ch) {\n .cards {\n flex-direction: column;\n }\n}\n\n\/* Style query *\/\n@container style(--theme: dark) {\n .cards {\n background-color: black;\n }\n}\n<\/code><\/pre>\n<p>Both types of container queries look for the closest ancestor with a corresponding <code>containment-type<\/code>. In a <code>style()<\/code> query, it will always be the parent since all elements have style containment by default. In this case, the direct parent of the <code>.cards<\/code> element in our ongoing example is the <code>.cards-container<\/code> element. If we want to query non-direct parents, we will need the <code>container-name<\/code> property to differentiate between containers when making a query.<\/p>\n<pre><code class=\"language-css\">.cards-container {\n container-name: cardsContainer;\n --theme: dark;\n}\n\n@container cardsContainer style(--theme: dark) {\n .card {\n color: white;\n }\n}\n<\/code><\/pre>\n<h2 id=\"weird-and-confusing-things-about-container-style-queries\">Weird and Confusing Things About Container Style Queries<\/h2>\n<p>Style queries are completely new and bring something never seen in CSS, so they are bound to have some confusing qualities as we wrap our heads around them — some that are completely intentional and well thought-out and some that are perhaps unintentional and may be updated in future versions of the specification.<\/p>\n<h3 id=\"style-and-size-containment-aren-t-mutually-exclusive\">Style and Size Containment Aren\u2019t Mutually Exclusive<\/h3>\n<p>One intentional perk, for example, is that a container can have both size and style containment. No one would fault you for expecting that size and style containment are mutually exclusive concerns, so setting an element to something like <code>container-type: inline-size<\/code> would make all style queries useless.<\/p>\n<p>However, another funny thing about container queries is that elements have style containment by default, and <em>there isn\u2019t really a way to remove it<\/em>. Check out this next example:<\/p>\n<pre><code class=\"language-css\">.cards-container {\n container-type: inline-size;\n --theme: dark;\n}\n\n@container style(--theme: dark) {\n .card {\n background-color: black;\n }\n}\n\n@container (width < 700px) {\n .card {\n background-color: red;\n }\n}\n<\/code><\/pre>\n<p>See that? We can still query the elements by style even when we explicitly set the <code>container-type<\/code> to <code>inline-size<\/code>. This seems contradictory at first, but it does make sense, considering that style and size queries are computed independently. It\u2019s better this way since both queries don\u2019t necessarily conflict with each other; a style query could change the colors in an element depending on a custom property, while a container query changes an element\u2019s <code>flex-direction<\/code> when it gets too small for its contents.<\/p>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"KKLyeQQ\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Conflicting Style and Size Queries [forked]](https:\/\/codepen.io\/smashingmag\/pen\/KKLyeQQ) by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/KKLyeQQ\">Conflicting Style and Size Queries [forked]<\/a> by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/figcaption><\/figure>\n<p>If active style and size queries are configured to apply conflicting styles, the most specific selector wins according to the cascade, so the elements have a black background color until the container gets below <code>700px<\/code> and the size query (which is written with more specificity in this example than the last one) kicks in.<\/p>\n<h3 id=\"unnamed-style-queries-check-every-ancestor-for-a-match\">Unnamed Style Queries Check Every Ancestor For a Match<\/h3>\n<p>In that last example, you may have noticed another weird thing in style queries: The <code>.card<\/code> element is inside an unnamed style query, so since all elements have a style containment, it should be querying its parent, <code>.cards<\/code>. However, the <code>.cards<\/code> element doesn\u2019t have any kind of <code>--theme<\/code> property; it\u2019s the <code>.cards-container<\/code> element that does, but the style query is active!<\/p>\n<pre><code class=\"language-css\">.cards-container {\n--theme: dark; \n}\n\n@container style(--theme: dark) {\n \/* This is still active! *\/\n .card {\n background-color: black;\n }\n}\n<\/code><\/pre>\n<p>How does this happen? Don\u2019t unnamed style queries query only their parent element? Well, not exactly. If the parent doesn\u2019t have the custom property, then the unnamed style query looks for ancestors higher up the chain. If we add a <code>--theme<\/code> property on the parent with another value, you will see the style query will use that element as the container, and the query no longer matches.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">.cards-container {\n--theme: dark; \n}\n\n.cards {\n --theme: light; \/* This is now the matching container for the query *\/\n}\n\n\/* This query is no longer active! *\/\n@container style(--theme: dark) {\n .card {\n background-color: black;\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>I don\u2019t know how intentional this behavior is, but it shows how messy things can get when working with unnamed containers.<\/p>\n<h3 id=\"elements-are-style-containers-by-default-but-styles-queries-are-not\">Elements Are Style Containers By Default, But Styles Queries Are Not<\/h3>\n<p>The fact that <em>style<\/em> queries are the default <code>container-type<\/code> and <em>size<\/em> is the default query <a href=\"https:\/\/geoffgraham.me\/funny-container-query-defaults\/\">seems a little mismatched<\/a> in that we have to explicitly declare a <code>style()<\/code> function to write the default type of query while there is no corresponding <code>size()<\/code> function. This isn\u2019t a knock on the specification, but one of those things in CSS you just have to remember.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"what-are-container-style-queries-good-for\">What Are Container Style Queries Good For?<\/h2>\n<p>At first look, style queries seem like a feature that opens up endless possibilities. But after playing with them, you don\u2019t really get a clear idea of what problem they\u2019d solve — at least not after the time I\u2019ve spent with them. All the use cases I\u2019ve seen or thought up aren\u2019t immediately all that useful, and the most obvious ones solve problems with well-established solutions already in place. A new way of writing CSS based on styles reacting to other styles seems liberating (the more ways to write CSS, the merrier!), but if we\u2019re already having trouble generally <a href=\"https:\/\/css-tricks.com\/naming-things-is-only-getting-harder\/\">naming things<\/a>, imagine how tough managing and maintaining those states and styles can be.<\/p>\n<p>I know all these are bold statements, but they aren\u2019t unfounded, so let me unpack them.<\/p>\n<h3 id=\"the-most-obvious-use-case\">The Most Obvious Use Case<\/h3>\n<p>As we\u2019ve discussed, we can only query custom properties with style queries at the moment, so the clearest use for them is storing bits of state that can be used to change UI styles when they change.<\/p>\n<p>Let\u2019s say we have a web app, perhaps a game featuring a global leaderboard of top players. Each item in the leaderboard is a component based on a player that needs different styling depending on that player\u2019s position on the leaderboard. We could style the first-place player with a gold background, the second-place player with silver, and the third-place with bronze, while the remaining players are all styled with the same background color.<\/p>\n<p>Let\u2019s also assume that this is not a static leaderboard. Players change places as their scores change. That means we\u2019re most likely serving the using a server-side rendering (SSR) framework to keep the leaderboard up to date with the latest data, so we could insert that data into the UI through inline styles with each position as a custom property, <code>--position: number<\/code>.<\/p>\n<p>Here\u2019s how we might structure the markup:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-html\"><ol>\n <li class=\"item-container\" style=\"--position: 1\">\n <div class=\"item\">\n <img src=\"...\" alt=\"Roi's avatar\" \/>\n <h2>Roi<\/h2>\n <\/div>\n <\/li>\n <li class=\"item-container\" style=\"--position: 2\"><!-- etc. --><\/li>\n <li class=\"item-container\" style=\"--position: 3\"><!-- etc. --><\/li>\n <li class=\"item-container\" style=\"--position: 4\"><!-- etc. --><\/li>\n <li class=\"item-container\" style=\"--position: 5\"><!-- etc. --><\/li>\n<\/ol>\n<\/code><\/pre>\n<\/div>\n<p>Now, we can use style queries to check the current item\u2019s position and apply their respective shiny backgrounds to the leaderboard.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-css\">.item-container {\n container-name: leaderboard;\n \/* No need to apply container-type: normal *\/\n}\n\n@container leaderboard style(--position: 1) {\n .item {\n background: linear-gradient(45deg, yellow, orange); \/* gold *\/\n }\n}\n\n@container leaderboard style(--position: 2) {\n .item {\n background: linear-gradient(45deg, grey, white); \/* silver *\/\n }\n}\n\n@container leaderboard style(--position: 3) {\n .item {\n background: linear-gradient(45deg, brown, peru); \/* bronze *\/\n }\n}\n<\/code><\/pre>\n<\/div>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"vYwWrRL\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Style Queries Use Case [forked]](https:\/\/codepen.io\/smashingmag\/pen\/vYwWrRL) by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/vYwWrRL\">Style Queries Use Case [forked]<\/a> by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/figcaption><\/figure>\n<p>Some browser clients don\u2019t fully support style queries from an embedded CodePen, but it should work if you fully open the demo in another tab. In any case, here is a screenshot of how it looks just in case.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/what-are-css-container-style-queries-good-for\/2-two-leaderboard-ui.jpg\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"450\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Two leaderboard UIs. One before style queries and one after styles have been applied\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/what-are-css-container-style-queries-good-for\/2-two-leaderboard-ui.jpg\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Figure 2: We can apply styles to an element\u2019s children and descendants when the element\u2019s styles match a certain condition. (<a href=\"https:\/\/files.smashing.media\/articles\/what-are-css-container-style-queries-good-for\/2-two-leaderboard-ui.jpg\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h3 id=\"but-we-can-achieve-the-same-thing-with-css-classes-and-ids\">But We Can Achieve the Same Thing With CSS Classes and IDs<\/h3>\n<p>Most container query guides and tutorials I\u2019ve seen use similar examples to demonstrate the general concept, but I can\u2019t stop thinking no matter how cool style queries are, we can achieve the same result using classes or IDs and with less boilerplate. Instead of passing the state as an inline style, we could simply add it as a class.<\/p>\n<pre><code class=\"language-html\"><ol>\n <li class=\"item first\">\n <img src=\"...\" alt=\"Roi's avatar\" \/>\n <h2>Roi<\/h2>\n <\/li>\n <li class=\"item second\"><!-- etc. --><\/li>\n <li class=\"item third\"><!-- etc. --><\/li>\n <li class=\"item\"><!-- etc. --><\/li>\n <li class=\"item\"><!-- etc. --><\/li>\n<\/ol>\n<\/code><\/pre>\n<p>Alternatively, we could add the position number directly inside an <code>id<\/code> so we don\u2019t have to convert the number into a string:<\/p>\n<pre><code class=\"language-html\"><ol>\n <li class=\"item\" id=\"item-1\">\n <img src=\"...\" alt=\"Roi's avatar\" \/>\n <h2>Roi<\/h2>\n <\/li>\n <li class=\"item\" id=\"item-2\"><!-- etc. --><\/li>\n <li class=\"item\" id=\"item-3\"><!-- etc. --><\/li>\n <li class=\"item\" id=\"item-4\"><!-- etc. --><\/li>\n <li class=\"item\" id=\"item-5\"><!-- etc. --><\/li>\n<\/ol>\n<\/code><\/pre>\n<p>Both of these approaches leave us with cleaner HTML than the container queries approach. With style queries, we have to wrap our elements inside a container — even if we don\u2019t semantically need it — because of the fact that containers (rightly) are unable to style themselves.<\/p>\n<p>We also have less boilerplate-y code on the CSS side:<\/p>\n<pre><code class=\"language-css\">#item-1 {\n background: linear-gradient(45deg, yellow, orange); \n}\n\n#item-2 {\n background: linear-gradient(45deg, grey, white);\n}\n\n#item-3 {\n background: linear-gradient(45deg, brown, peru);\n}\n<\/code><\/pre>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"oNRoydN\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Style Queries Use Case Replaced with Classes [forked]](https:\/\/codepen.io\/smashingmag\/pen\/oNRoydN) by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/oNRoydN\">Style Queries Use Case Replaced with Classes [forked]<\/a> by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/figcaption><\/figure>\n<p>As an aside, I know that using IDs as styling hooks is often viewed as a no-no, but that\u2019s only because IDs must be unique in the sense that no two instances of the same ID are on the page at the same time. In this instance, there will never be more than one first-place, second-place, or third-place player on the page, making IDs a safe and appropriate choice in this situation. But, yes, we could also use some other type of selector, say a <code>data-*<\/code> attribute.<\/p>\n<p>There is something that could add a lot of value to style queries: <strong>a range syntax for querying styles<\/strong>. This is an open feature that <a href=\"https:\/\/github.com\/w3c\/csswg-drafts\/issues\/8376\">Miriam Suzanne proposed in 2023<\/a>, the idea being that it queries numerical values using range comparisons just like size queries.<\/p>\n<p>Imagine if we wanted to apply a light purple background color to the rest of the top ten players in the leaderboard example. Instead of adding a query for each position from four to ten, we could add a query that checks a range of values. The syntax is obviously not in the spec at this time, but let\u2019s say it looks something like this just to push the point across:<\/p>\n<pre><code class=\"language-css\">\/* Do not try this at home! *\/\n@container leaderboard style(4 >= --position <= 10) {\n .item {\n background: linear-gradient(45deg, purple, fuchsia);\n }\n}\n<\/code><\/pre>\n<p>In this fictional and hypothetical example, we\u2019re:<\/p>\n<ul>\n<li>Tracking a container called <code>leaderboard<\/code>,<\/li>\n<li>Making a <code>style()<\/code> query against the container,<\/li>\n<li>Evaluating the <code>--position<\/code> custom property,<\/li>\n<li>Looking for a condition where the custom property is set to a value equal to a number that is greater than or equal to <code>4<\/code> and less than or equal to <code>10<\/code>.<\/li>\n<li>If the custom property is a value within that range, we set a player\u2019s background color to a <code>linear-gradient()<\/code> that goes from <code>purple<\/code> to <code>fuschia<\/code>.<\/li>\n<\/ul>\n<p>This is <em>very<\/em> cool, but if this kind of behavior is likely to be done using components in modern frameworks, like React or Vue, we could also set up a range in JavaScript and toggle on a <code>.top-ten<\/code> class when the condition is met.<\/p>\n<figure class=\"break-out\">\n<p data-height=\"480\" data-theme-id=\"light\" data-slug-hash=\"OJYOEZp\" data-user=\"smashingmag\" data-default-tab=\"result\" class=\"codepen\">See the Pen [Style Ranged Queries Use Case Replaced with Classes [forked]](https:\/\/codepen.io\/smashingmag\/pen\/OJYOEZp) by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/p><figcaption>See the Pen <a href=\"https:\/\/codepen.io\/smashingmag\/pen\/OJYOEZp\">Style Ranged Queries Use Case Replaced with Classes [forked]<\/a> by <a href=\"https:\/\/codepen.io\/monknow\">Monknow<\/a>.<\/figcaption><\/figure>\n<p>Sure, it\u2019s great to see that we can do this sort of thing directly in CSS, but it\u2019s also something with an existing well-established solution.<\/p>\n<h3 id=\"separating-style-logic-from-logic-logic\">Separating Style Logic From <em>Logic<\/em> Logic<\/h3>\n<p>So far, style queries don\u2019t seem to be the most convenient solution for the leaderboard use case we looked at, but I wouldn\u2019t deem them useless solely because we can achieve the same thing with JavaScript. I am a big advocate of reaching for JavaScript only when necessary and only in sprinkles, but style queries, the ones where we can only check for custom properties, are most likely to be useful when paired with a UI framework where we can easily reach for JavaScript within a component. I have been using <a href=\"https:\/\/astro.build\">Astro<\/a> an awful lot lately, and in that context, I don\u2019t see why I would choose a style query over programmatically changing a class or ID.<\/p>\n<p>However, a case can be made that implementing style logic inside a component is messy. Maybe we should keep the logic regarding styles in the CSS away from the rest of the <em>logic<\/em> logic, i.e., the stateful changes inside a component like conditional rendering or functions like <code>useState<\/code> and <code>useEffect<\/code> in React. The <em>style logic<\/em> would be the conditional checks we do to add or remove class names or IDs in order to change styles.<\/p>\n<p>If we backtrack to our leaderboard example, checking a player\u2019s position to apply different styles would be style logic. We could indeed check that a player\u2019s leaderboard position is between four and ten using JavaScript to programmatically add a <code>.top-ten<\/code> class, but it would mean leaking our style logic into our component. In React (for familiarity, but it would be similar to other frameworks), the component may look like this:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">const LeaderboardItem = ({position}) => {\n <li className={`item ${position >= 4 && position <= 10 ? \"top-ten\" : \"\"}`} id={`item-${position}`}>\n <img src=\"...\" alt=\"Roi's avatar\" \/>\n <h2>Roi<\/h2>\n <\/li>;\n};\n<\/code><\/pre>\n<\/div>\n<p>Besides this being ugly-looking code, adding the style logic in JSX can get messy. Meanwhile, style queries can pass the <code>--position<\/code> value to the styles and handle the logic directly in the CSS where it is being used.<\/p>\n<pre><code class=\"language-javascript\">const LeaderboardItem = ({position}) => {\n <li className=\"item\" style={{\"--position\": position}}>\n <img src=\"...\" alt=\"Roi's avatar\" \/>\n <h2>Roi<\/h2>\n <\/li>;\n};\n<\/code><\/pre>\n<p>Much cleaner, and I think this is closer to the value proposition of style queries. But at the same time, this example makes a large leap of assumption that we will get a range syntax for style queries at some point, which is not a done deal.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>There are lots of teams working on making modern CSS better, and not all features have to be groundbreaking miraculous additions.<\/p>\n<blockquote class=\"pull-quote\">\n<p>\n <a class=\"pull-quote__link\" aria-label=\"Share on Twitter\" href=\"https:\/\/twitter.com\/share?text=%0aSize%20queries%20are%20definitely%20an%20upgrade%20from%20media%20queries%20for%20responsive%20design,%20but%20style%20queries%20appear%20to%20be%20more%20of%20a%20solution%20looking%20for%20a%20problem.%0a&url=https:\/\/smashingmagazine.com%2f2024%2f06%2fwhat-are-css-container-style-queries-good-for%2f\"><\/p>\n<p>Size queries are definitely an upgrade from media queries for responsive design, but style queries appear to be more of a solution looking for a problem.<\/p>\n<p> <\/a>\n <\/p>\n<div class=\"pull-quote__quotation\">\n<div class=\"pull-quote__bg\">\n <span class=\"pull-quote__symbol\">\u201c<\/span><\/div>\n<\/p><\/div>\n<\/blockquote>\n<p>It simply doesn\u2019t solve any specific issue or is better enough to replace other approaches, at least as far as I am aware.<\/p>\n<p>Even if, in the future, style queries will be able to check for any property, that introduces a whole new can of worms where styles are capable of reacting to other styles. This seems exciting at first, but I can\u2019t shake the feeling it would be unnecessary and even chaotic: styles reacting to styles, reacting to styles, and so on with an unnecessary side of boilerplate. I\u2019d argue that a more prudent approach is to write all your styles declaratively together in one place.<\/p>\n<p>Maybe it would be useful for web extensions (like <a href=\"https:\/\/darkreader.org\">Dark Reader<\/a>) so they can better check styles in third-party websites? I can\u2019t clearly see it. If you have any suggestions on how CSS Container Style Queries can be used to write better CSS that I may have overlooked, please let me know in the comments! I\u2019d love to know how you\u2019re thinking about them and the sorts of ways you imagine yourself using them in your work.<\/p>\n<div class=\"signature\">\n <img decoding=\"async\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" class=\"lazyload\" data-src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\"><br \/>\n <span>(gg, yk)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>What Are CSS Container Style Queries Good For? What Are […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[],"class_list":["post-305","post","type-post","status-publish","format-standard","hentry","category-css"],"_links":{"self":[{"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/posts\/305","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/comments?post=305"}],"version-history":[{"count":1,"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/posts\/305\/revisions"}],"predecessor-version":[{"id":306,"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/posts\/305\/revisions\/306"}],"wp:attachment":[{"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/media?parent=305"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/categories?post=305"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blissfuldebt.com\/index.php\/wp-json\/wp\/v2\/tags?post=305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}