Container Queries - The inevitable end of mobile first

Table of Contents

What is mobile first?

With the ever-growing group of smartphone users in the early 2010s, a problem quickly became apparent. It is difficult to pass on all the information that is presented to a desktop user on the web to a smartphone user in the same form. However, the latter should of course not be disadvantaged in terms of functionality and information value. For this reason, the companies began to design and develop with a focus on mobile devices. As it is easier to scale designs up than down. The “mobile first” approach was born.

Over the coming years, this approach became more and more established and became the standard for new projects on the web. Today, more than 60 percent of all web traffic comes from mobile devices, and the trend is growing.

So why deviate from this approach?

The NEW web

Over the last few years, websites and web applications have developed at an unprecedented pace - both in terms of technology and architecture.

Large parts are moving to the cloud, and edge hosting and computing are becoming increasingly important. With this comes greater flexibility and the need for more dynamic and scalable design systems.

Design systems

Despite their newfound flexibility, most large companies rely on design systems to present a consistent image to the outside world.

Design systems are standardized collections of designs and components built to be shared and reused across software and platforms.

However, it is precisely this often cross-media use of components (e.g. website, app) that causes problems for many design systems. While it is not a problem for designers to design components in isolation, developers in particular often reach the limits of what is technically possible natively when implementing design systems.

Rethinking responsiveness

While media queries were a blessing for an entire industry in the early 2010s, helping to drive the adoption of smartphones, today they are often a hurdle to the adoption of design systems.

The limits of media queries

But what exactly is the problem with Media Queries, and why don’t we just extend the Media Queries spec with a solution?

Let’s take a look at the questions and the corresponding answers to get a clearer picture of the problem.

What is the problem with media queries?

The answer here is actually obvious, as the name gives it away. Media queries are, as the name implies, designed to apply different styles to our website or components based on media characteristic.

But what happens if a component appears in different places or in different contexts on the same page? Let’s take a closer look at the following illustration to better understand the problem.

Reused component on a website

Here is a wireframe of a conventional website. Let’s say it’s a product overview page. You can quickly see that the content of all the elements is identical - image, headline, and text. So it would make perfect sense to say that all these elements are the same component, and their styles are adjusted according to their contexts.

However, this is where the limits of media queries are, as contextual differences cannot be taken into account when styling, as these are not media differences. The medium is the same for all components displayed on the user’s device.

Let’s take a look at the problem with a code example, analogous to the illustration above.

The following is the markup of a product tile:

<article class="productTile">
  <img 
    class="productTile__image" 
    src="/product.webp" 
    alt="Product Image"
  >
  <div class="productTile__content">
    <h2 class="productTile__headline">Product Name</h2>
    <p class="productTile__text">Product Description</p>
  </div>
</article>

The easiest and quickest way to date to customize the tile depending on the context was to bind the stylings to the corresponding context.

.productTile {
 display: grid;
 gap: 2rem;
}

/* 
 * When inside of the main content the image 
 * and content should be displayed next to each other 
 */
:where(main .productTile) {
 grid-template-columns: 1fr 2fr;
}

However, this would completely defeat the purpose of a design system, because this very small change alone has removed the isolation of our component and made its representation dependent on other external factors, thereby limiting its flexibility, increasing its susceptibility to errors, and reducing its maintainability.

Simply renaming the .article class to .tile, for example, would invalidate the entire context-specific styles in this case. All in all, not a good solution.

On to the second question:

Why don’t we simply extend the Media Queries spec with a corresponding solution?

The answer to this question can actually also be derived from the name. Media queries have their use case in the differentiation of styles according to certain media characteristic. In other words, our problem does not match our solution, so we need a different solution.

However, even though these problems have existed for years, it took until the beginning of this year for browsers to start rolling out the long-awaited correct solution in the form of container queries in their stable versions.

Let’s finally take a look at the long-teased feature, what it does, how to use it, and what extensions we can look forward to in the future.

What are container queries?

Container queries start where media queries have their limits. They allow elements to be styled depending on their context and thus create a way to implement responsive and reusable components natively without hacky workarounds. Exactly what you would want for a real design system.

To go into more detail about what this means for our components, let’s take another look at the illustration from the section What’s the problem with media queries? in a slightly modified form.

Reused component with border on a website

You can immediately see that there is a bit more color in the picture, but what exactly are these dark blue boxes supposed to show?

As already mentioned in the intro, we can use container queries to style elements based on their context and thus solve unsolvable problems with media queries. In this particular example, we can define either the different content areas, i.e. sidebar and main content, or the different individual tiles as containers and adjust their styling according to their size.

If we now want to style the individual product tiles with the help of container queries analogous to the example from What’s the problem with media queries? the whole thing looks like this:

<article class="productTile">
 <div class="productTile__inner">
   <img 
    class="productTile__image" 
    src="/product.avif" 
    alt="Product Image"
   >
   <div class="productTile__content">
     <h2 class="productTile__headline">Product Name</h2>
     <p class="productTile__text">Product Description</p>
   </div>
 </div>
</article>
/* Create a new named container called productTile */
:where(.productTile) {
  container: productTile / inline-size;
}

/* Same base styling as before */
:where(.productTile__inner) {
  display: grid;
  gap: 2rem;
}

/* Container Query targeting a named container */
@container productTile (inline-size > 900px) {
  :where(.productTile__inner) {
    grid-template-columns: 1fr 2fr;
  }
}

A few changes quickly catch the eye, especially in the CSS. For example, we have added new styles to our .productTile in the form of container: productTile / inline-size;, as well as some new at-rules such as @container (inline-size > 600px).

Let’s first take a look at the changes to .productTile.

By setting container: productTile / inline-size; we define our HTML element as the root element of a new containment context, and thus as a container that can be used in container queries.

container: productTile / inline-size; is only the short hand of the properties and values container-type: inline-size; and container-name: productTile;.

While container-name is optional when creating a new container, container-type is required to inform the browser that the container is a container and what type it is (both are required when using the container short form). The following container types are available:

inline-size and size are container size query keywords and are therefore used for size-based container queries. inline-size specifically defines a container that listens only to changes along the X-axis or, according to the logical system, the inline axis, while containers of type size type listen to changes along both axes.

container-type: normal; is a bit of a special case. While normal would create a new container context, it would not listen to any changes in the size of the container. We will take a look at the use cases for container queries outside of size relative use cases and what the normal keyword can be used for in this context later in the outlook.

Even though container-name is only an optional keyword in the definition of containers, as mentioned above, it has a much more interesting functionality in addition to the purely structural naming of containers, which only becomes apparent when you take a closer look at the container query itself.

Let’s take a look at the following line from the previous example: @container productTile (inline-size > 900px). While anonymous container queries such as @container (inline-size > 900px) always reference the nearest parent container, named container queries can reference any parent container. This can be a very powerful feature, especially in complex layouts where multiple containers are nested, because just like media queries, container queries can be nested, allowing you to map dependencies on multiple containers in a single query.

Let’s take another look at an example to understand the different ways in which containers can be defined and used in container queries.

/* Container definition short hand (name / type) */
:where(.pageLayout) {
  container: pageLayout/ inline-size;
}

/* Container definition in long form */
:where(.productTile) {
  container-type: inline-size;
  container-name: productTile;
}

/* Anonymous container definition */
:where(.section) {
  container-type: inline-size;
}

/* Anonymous container query - Next parent container */
@container (inline-size > 600px) {
  :where(.productTile__inner) { ... }
}


/* Named container query - Specific parent Container */
@container productTile (inline-size > 600px) {
  :where(.productTile__inner) { ... }
}


/* Nested container queries - Dependencies on several containers */
@container productTile (inline-size > 400px) {
  @container pageLayout (inline-size > 800px) {
    :where(.productTile__inner) { ... }
  }
}

Container Query Conditions

In addition to inline-size, which was used a lot in the previous examples, there are a few other container query conditions that work in the same way as their media query counterparts.

The following conditions can be used:

Also the following dimensional conditions exist including their min- and max- counterparts:

As with media queries, these can be used in combination with logical operators. Specifically, the following operators are allowed:

Container Query Length Units

To ensure maximum styling flexibility in connection with container queries, some new length units have been added to the CSS standard in parallel with their introduction. These are designed specifically for use within a container. However, when used outside a container, they refer to the root element of the DOM.

The container query length units are a counterpart to the viewport units. As a result, the following new units are available:

These units can be used in all CSS definitions that accept the <length> type.

Comparison

Now that we have looked at container queries enough, you may be asking yourself:

Do I still need media queries? Container queries cover almost all of my use cases.

While this statement may be true in some project contexts, both technologies have their place and are designed to be used in combination.

Let’s compare what differentiates the two technologies:

Media Queries

Container Queries

Looking at the lists above, we can see that container queries actually cover only a fraction of the functionality of media queries.

Outlook

Although container queries have only just landed in browsers, the CSS Working Group is already working on an extension to the standard in the form of container style queries.

While we have only talked about container size queries so far, container style queries open up a whole new area of use for container queries.

In the future, it will be possible to query container styles and style elements based on them. It will look like this:

/* Container style query depending on the 
 * background color of the container 
 */
@container productTile style(background: #000) {
  :where(.productTile__inner) { ... }
}


/* Container style query depending on the 
 * value of a custom property 
 */
@container productTile style(--round: true) {
  :where(.productTile__inner) { ... }
}

The second example is particularly interesting because it allows you to toggle different styles on different elements based on the value of a custom property without increasing their specificity.

This allows for flexible configuration of components via native CSS without the use of JavaScript and properties as we know it from many single page application frameworks and is therefore a real game changer.

This feature can be used on any container, i.e. on size containers, but also on containers of container type normal, which we briefly touched on earlier, once it is available in all browsers.

If you want to test the whole thing now, you can do so to some extent in Chrome, where the feature is already available with limited functionality. In particular, it can only check custom properties, not any container style.

Summary

The ability to use containers and media queries in combination in all modern browsers opens up new possibilities for both developers and designers to create responsive and flexible layouts and components.

Design systems and components can finally be implemented with the flexibility they should have had for years, saving large organizations time and money when implementing new user interfaces.

Even if container queries can still be seen as an experimental technology at the moment, it is only a matter of time before they become an integral part of today’s web. In combination with container style queries, much of the visual rendering logic currently found in JavaScript can be brought back into CSS and thus simplified.

All in all, web developers and designers can look forward to a bright future in which the communication problems between the parties as well as the complexity of implementation will be decimated.