Container Queries, the next step towards a truly modular CSS
Container Queries, the next step towards a truly modular CSS
By Maarten Van Hoof
11 min read
Container queries enables encapsulation of adaptive styling based on the size, style or state of a parent element. This allows responsive component-based architectures, like design systems and component libraries, to provide the most optimal responsive styling within a component itself.
- Authors
- Name
- Maarten Van Hoof
- linkedinMaarten Van Hoof
- twitter@mrtnvh
- Github
- githubvanhoofmaarten
- Website
- websiteBlog
*TL;DR Container queries enables encapsulation of adaptive styling based on the size, style or state of a parent element. This allows responsive component-based architectures, like design systems and component libraries, to provide the most optimal responsive styling within a component itself.*
Have you ever developed reusable components or a design system? If so, how did you ensure that the components were able to have the most optimal layout across different viewport sizes? Through media queries or by providing configuration options?
If my former questions are not ringing any bells: No worries. I'll share with you, how in the near future, you can develop robust, reusable, responsive component styles with something that I personally have been waiting for since I started developing responsive websites and - applications: container queries.
Towards a truly modular CSS
With having seen the evolution of front-end over the last couple of years, having contributed to several design systems and component libraries, and heck having even maintained a personal Bootstrap fork with alternate naming convention for half a decade, I believe container queries are one of the most exciting new features coming to the CSS standard. Something that will enable you to optimize responsive styling for your components. Let me give you an example.
Widgets
Let's say we're part of a project and our goal is to build this dashboard-page. If we take out of account the header and the sidebar for a second and look at the page's main content, we see a handful of widgets. A weather widget, a pie-chart showing our favourite bars, a bar graph showing our favourite pies, a weather widget and user list widget.
The following user stories have to implemented in this page:
- As a user, I should be able to customize my dashboard by resizing widgets.
- As a user, I want to see more information in larger widgets and less in smaller widgets. They should contain more or less information, depending on their available internal space.
If we choose a prioritize the weather widget for instance, next to only show todays weather, we could show additional information like the expected precipitation or the expected temperature for upcoming days.
Let's look at some other possible factors that could come in to play. Additional customizations options like a collapsible sidebar, for instance. What if we want to reuse these widgets or make them available to other teams or projects where we can't author the parent or final result these widgets will be part of?
How would we translate these requirements, with possible side-effects, to code?
First thing to we'd might take in to consideration is using media queries. However, media queries give us the ability to style responsively according to the viewport and doesn't offer us enough flexibility to create modular styling.
Possible solutions
How can each component be responsible for it's own adaptive styling?
Custom selectors
We could create custom classes or attributes per size. .component--large, .component--small, setting a attribute on the element and targeting that attribute with a selector in your CSS. With this solution however, the final application is responsible for declaring the correct styling of the widget. We have to create extra styling in the dashboard application It's still not possible to automatically provide the most optimal layout to the end-user. The end-user has to implement their own logic to handle this.
<internal-dashboard>
<external-widget type="bar" />
<external-widget type="pie" />
<external-widget type="weather" />
<external-widget type="users" />
</internal-dashboard>
internal-dashboard {
}
external-widget {
}
external-widget[type='gauge'] {
}
external-widget[type='pie'] {
}
external-widget[type='weather'] {
}
external-widget[type='users'] {
}
ResizeObserver
Or we could use ResizeObserver API, a browser API that through JavaScript can take an elements size into account and act accordingly. But, with this solution, we have to wait until the JavaScript is evaluated. Without the proper measures, like some form of loading screen and making sure this solution is loaded before every other piece of JavaScript is ready, it can cause a Flash of Unstyled Content.
const $widget = document.querySelector('.widget')
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.target.width > entry.contentBoxSize.inlineSize) {
entry.target.classList.add('widget--large')
} else {
entry.target.classList.remove('widget--large')
}
}
})
resizeObserver.observe($widget)
A CSS solution however, that is if we use the recommendation of loading critical styles upfront and avoiding render-blocking JavaScript on page load, is evaluated before JavaScript. Therefore, we'll receive the correct layout on first paint.
Luckily for us, something's cooking!
History
Container Queries, element queries, ... The possibility of querying each element its own dimensions is something we've been looking for since modular front-end architecture met responsive web design and has been on the discussion radar for over almost a decade now. Thanks to recent improvements to browser rendering engines and the advent of the CSS containment specification, container queries, which can lead to heavy layout calculations, can finally be implemented in a performant way without sacrificing anything of the user experience.
With container queries, the component itself owns all of its responsive styles. It's the optimal solution for responsive component-based architectures, like design systems and component libraries. It splits up responsibilities for styling our layout. For example, the larger parts of your webpage, things where a media query makes more sense, will be responsible for the larger layout, like grids and the smaller parts, like individual components will have their own responsibility for their own layout based on the available space they will have in the larger layout.
One single class to rule them all
In this example, created by the great Una Kravets, we see a page of an ecommerce platform where we can order several kinds of plants to spruce up our home offices. Each product component is implemented using the same CSS class. Using Container Queries, each product component will adapt to its most optimal layout according to the space available.
Syntax
Container Queries are a part of the CSS Containment Module Level 3, and the specification is currently a Working Draft, which means that the CSS working group is actively working on the feature. A basis has already been set and details are being ironed out as we speak. CSS containment allows us to improve rendering performance by isolation of a DOM subtree or in other words indicating that an element and its contents rendering should be handled independently of the rest of the document tree. That isolation is what enables us to query elements using container queries. CSS containment consists of four types: size, layout, style and paint. Containment can be set with a single type or multiple types at the same time.
Declaring containment
Old syntax
In the first proposed syntax for container queries, we had to set containment on size, layout and style.
.product {
contain: size layout style;
}
Current syntax
Currently the container query specification settles at more distinct property like container, which is a shorthand for container-type and container-name.
.product {
container: product / inline-size;
/* Shorthand for */
container-name: product;
container-type: inline-size;
}
Container-type
.product {
container-name: product;
container-type: inline-size;
}
With the container-type you can establish an element as a query container. Currently the following container types are in discussion:
Size container features
@container (inline-size > 400px) {
.product-body {
}
}
Size container feature are:
- size, to query both horizontal and vertical axis,
- inline-size or block-size, the logical properties for width and height for respectively only the horizontal or vertical axis,
- aspect/ratio
- orientation
Style container features
Style container features can be used for querying computed values. With this, we can query the container's computed values. For example, if the background-colour of the container is red, we can act and style appropriately.
.product {
container-type: style;
}
@container style(background: red) {
.product-body {
}
}
State container features
State container features can be used for querying miscellaneous container states. This will allow us to query certain states of a container. One example is querying if a container with the property position: sticky is in its sticky state. Do keep in mind that the syntax shown here, again, is a proposal and is likely bound to change.
header {
container: is-stuck is-visible / header;
position: sticky;
top: 0;
}
@container header (is-stuck) {
/* … */
}
@container header (is-visible) {
/* … */
}
Container-name
The container-name property enables us to implement multi-level container queries. With container names you can target specific containers to query.
.product-list {
container-type: inline-size;
container-name: list;
}
.page {
container-type: inline-size;
container-name: folio;
}
@container list (inline-size > 800px) {
.product {
/* … */
}
}
@container folio (inline-size > 400px) and (inline-size < 800px) {
.product {
/* … */
}
}
Here our first query will target .product-list
through the list
container-name and the second query will target .page
through the folio
container-name.
Querying the container
The actual container query is declared using the @container
rule, which has a similar syntax to the @media
rule or media query with the addition of also declaring the container-type. In this example, the query will match if the inline-size, the logical property of width, is larger than 400 pixels. Like media queries, we can use multiple conditions. Here, the query will match if the inline-size exceeds 400 pixels and block-size exceeds 200 pixels.
@container (inline-size > 400px) {
.product-body {
/* … */
}
}
To make use of container-names, we need to declare the container-name. In the example below, we filter the query to the container with container-name
list
.
@container list (inline-size > 800px) {
.product {
/* … */
}
}
An important note with using container queries is that container cannot query themselves. Containment always has to be set on an ancestor, in order for container queries to match.
.ancestor {
container-type: inline-size;
}
@container (inline-size > 800px) {
.ancestor {
/* NOPE */
}
}
Container Relative units
Similar to viewport relative units, we have container relative units, which allow you to use dimensions of a container as a unit. Like where 1 v double-u equals one percent of the viewport width, we could have one 'cee que double-u' that would equal one percent of the container's width
unit | relative to |
---|---|
cqw | 1% of a query container’s width |
cqh | 1% of aquery container’s height |
cqi | 1% of a query container’s inline size |
cqb | 1% of a query container’s block size |
cqmin | The smaller value of cqi or cqb |
cqmax | The larger value of cqi or cqb |
Our widgets with container queries
With the knowledge of container queries in our possession, we can refactor the use of custom classes or ResizeObserver to container queries. Keep in mind, we do have to add an extra wrapper if we want to style the base of the widget, as container queries can only query ancestors.
<!-- external-widget-component -->
<div className="widget">
<div className="widget-body">
<!-- widget-content -->
</div>
</div>
.widget {
container: inline-size;
}
@container (inline-size > 500px) {
.widget-body {
/* … */
}
}
Experiment today!
Although the specification is still in active development, which means the syntax still subject to possible change, you can experiment with Container Queries today. Chrome Canary has experimental support for Container Queries behind the enable-container-queries flag and there is a JavaScript polyfill availble to enable the Container Query functionality in other browsers.
However, knowing the disadvantages of using JavaScript as a solution for this particular problem, like the Flash of unstyled content, I personally would not recommend using this in production just yet.
Examples
Shopping cart component
A good use case for container queries, I believe, is a shopping cart component, where we have declared all our shopping cart business logic in one single place, like the calculated subtotal of the quantity and price of a product and the calculation of the grand total. With container queries, we can then re-use that single component, for example, on the actual shopping cart page or in the header as a mini cart. Depending on the size given to the component, it will adapt its styling. The larger desktop version of this component is, thanks to container queries, also immediately optimized for mobile devices.
Responsive inline SVGs and SVG sprites
We can even go as far as using container queries in our SVGs, and by extension SVG-sprites. SVGs support CSS and will also support container queries. By declaring responsive styles in a SVG or SVG-sprite, we can create truly responsive SVG and SVG-sprites. In this experiment, we see SVG images of a stegosaurs and a diplodocus, both are part of the same SVG-sprite, declared at the top of the document and are used in the main part of the document.
If I enlarge the image of the stegosaurus, we see the outline disappear and the same goes for the diplodocus, only at a larger size. Container queries tailored to each image and which are declared once in the SVG-sprite make this possible. Go check out the CodePen later on for more information.
#TIL
Let's bring this to a close, shall we? What did we learn today?
With container queries, we can encapsulate adaptive styling in elements. It's the optimal solution for responsive component-based architectures, like design systems and component libraries.
The Container Queries umbrella is not limited to only querying dimensions. Querying computed styles, certain element states, new container relative units and several more features, will be part of the specification.
Current state is that the specification is a Working Draft and under active development. It's available for experimentation in Chrome Canary behind a feature flag or with the container-query-polyfill. With that, I would not recommend it using it in production just yet.
Have fun!
Resources
- CSS Containment Module Level 3
- Container Queries [css-contain] GitHub Project
- Awesome-Container-Queries
- Shopping Cart example
- Responsive SVG sprite example
Upcoming events
The Test Automation Meetup
PLEASE RSVP SO THAT WE KNOW HOW MUCH FOOD WE WILL NEED Test automation is a cornerstone of effective software development. It's about creating robust, predictable test suites that enhance quality and reliability. By diving into automation, you're architecting systems that ensure consistency and catch issues early. This expertise not only improves the development process but also broadens your skillset, making you a more versatile team member. Whether you're a developer looking to enhance your testing skills or a QA professional aiming to dive deeper into automation, RSVP for an evening of learning, delicious food, and the fusion of coding and quality assurance! 🚀🚀 18:00 – 🚪 Doors open to the public 18:15 – 🍕 Let’s eat 19:00 – 📢 First round of Talks 19:45 – 🍹 Small break 20:00 – 📢 Second round of Talks 20:45 – 🍻 Drinks 21:00 – 🙋♀️ See you next time? First Round of Talks: The Power of Cross-browser Component Testing - Clarke Verdel, SR. Front-end Developer at iO How can you use Component Testing to ensure consistency cross-browser? Second Round of Talks: Omg who wrote this **** code!? - Erwin Heitzman, SR. Test Automation Engineer at Rabobank How can tests help you and your team? Beyond the Unit Test - Christian Würthner, SR. Android Developer at iO How can you do advanced automated testing for, for instance, biometrics? RSVP now to secure your spot, and let's explore the fascinating world of test automation together!
| Coven of Wisdom - Amsterdam
Go to page for The Test Automation MeetupCoven of Wisdom - Herentals - Winter `24 edition
Worstelen jij en je team met automated testing en performance? Kom naar onze meetup waar ervaren sprekers hun inzichten en ervaringen delen over het bouwen van robuuste en efficiënte applicaties. Schrijf je in voor een avond vol kennis, heerlijk eten en een mix van creativiteit en technologie! 🚀 18:00 – 🚪 Deuren open 18:15 – 🍕 Food & drinks 19:00 – 📢 Talk 1 20:00 – 🍹 Kleine pauze 20:15 – 📢 Talk 2 21:00 – 🙋♀️ Drinks 22:00 – 🍻 Tot de volgende keer? Tijdens deze meetup gaan we dieper in op automated testing en performance. Onze sprekers delen heel wat praktische inzichten en ervaringen. Ze vertellen je hoe je effectieve geautomatiseerde tests kunt schrijven en onderhouden, en hoe je de prestaties van je applicatie kunt optimaliseren. Houd onze updates in de gaten voor meer informatie over de sprekers en hun specifieke onderwerpen. Over iO Wij zijn iO: een groeiend team van experts die end-to-end-diensten aanbieden voor communicatie en digitale transformatie. We denken groot en werken lokaal. Aan strategie, creatie, content, marketing en technologie. In nauwe samenwerking met onze klanten om hun merken te versterken, hun digitale systemen te verbeteren en hun toekomstbestendige groei veilig te stellen. We helpen klanten niet alleen hun zakelijke doelen te bereiken. Samen verkennen en benutten we de eindeloze mogelijkheden die markten in constante verandering bieden. De springplank voor die visie is talent. Onze campus is onze broedplaats voor innovatie, die een omgeving creëert die talent de ruimte en stimulans geeft die het nodig heeft om te ontkiemen, te ontwikkelen en te floreren. Want werken aan de infinite opportunities van morgen, dat doen we vandaag.
| Coven of Wisdom Herentals
Go to page for Coven of Wisdom - Herentals - Winter `24 editionMastering Event-Driven Design
PLEASE RSVP SO THAT WE KNOW HOW MUCH FOOD WE WILL NEED Are you and your team struggling with event-driven microservices? Join us for a meetup with Mehmet Akif Tütüncü, a senior software engineer, who has given multiple great talks so far and Allard Buijze founder of CTO and founder of AxonIQ, who built the fundaments of the Axon Framework. RSVP for an evening of learning, delicious food, and the fusion of creativity and tech! 🚀 18:00 – 🚪 Doors open to the public 18:15 – 🍕 Let’s eat 19:00 – 📢 Getting Your Axe On Event Sourcing with Axon Framework 20:00 – 🍹 Small break 20:15 – 📢 Event-Driven Microservices - Beyond the Fairy Tale 21:00 – 🙋♀️ drinks 22:00 – 🍻 See you next time? Details: Getting Your Axe On - Event Sourcing with Axon Framework In this presentation, we will explore the basics of event-driven architecture using Axon Framework. We'll start by explaining key concepts such as Event Sourcing and Command Query Responsibility Segregation (CQRS), and how they can improve the scalability and maintainability of modern applications. You will learn what Axon Framework is, how it simplifies implementing these patterns, and see hands-on examples of setting up a project with Axon Framework and Spring Boot. Whether you are new to these concepts or looking to understand them more, this session will provide practical insights and tools to help you build resilient and efficient applications. Event-Driven Microservices - Beyond the Fairy Tale Our applications need to be faster, better, bigger, smarter, and more enjoyable to meet our demanding end-users needs. In recent years, the way we build, run, and operate our software has changed significantly. We use scalable platforms to deploy and manage our applications. Instead of big monolithic deployment applications, we now deploy small, functionally consistent components as microservices. Problem. Solved. Right? Unfortunately, for most of us, microservices, and especially their event-driven variants, do not deliver on the beautiful, fairy-tale-like promises that surround them.In this session, Allard will share a different take on microservices. We will see that not much has changed in how we build software, which is why so many “microservices projects” fail nowadays. What lessons can we learn from concepts like DDD, CQRS, and Event Sourcing to help manage the complexity of our systems? He will also show how message-driven communication allows us to focus on finding the boundaries of functionally cohesive components, which we can evolve into microservices should the need arise.
| Coven of Wisdom - Utrecht
Go to page for Mastering Event-Driven Design