Most developers probably learned and created responsive designs using Media Queries for the first time.
When building a responsive web, media queries allow you to change the layout based on the viewport size. However, during development, there are many times when the design needs to change based on the space the component actually occupies, rather than the viewport.
For example, the same component might be placed in a wide main area or a narrow sidebar area. Since the space occupied by the two areas is different, the same design cannot be applied, and the layout is likely to break in the narrow area. Media queries have difficulty handling these situations properly. In such cases, using container queries allows you to create truly reusable components that respond to the size of the parent element.
Limitations of Media Queries
Let's look at the situation mentioned above in more detail.
How do Media Queries Work?
Media queries apply styles based on the size of the viewport.
/* When viewport width is 768px or more */
@media (min-width: 768px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}This code is logic that says "if the screen is wide, show the card horizontally." While this seems sufficient in viewport-centric design, problems arise in component-centric design.
Real-world Situations
When the same component is used in different sizes in multiple places
There is a newsletter subscription component. On desktop (1280px), the input and button are arranged horizontally, and when it's smaller than that, they are arranged vertically. Let's say we place this component in a wide area (800px) and a narrow area (300px) on a desktop screen.
Since media queries only look at the viewport, they judge "it's larger than 1280px, so I should apply the horizontal layout!", and as a result, the horizontal layout is applied even in the narrow area, causing the component to break.
The limitations of media queries can be summarized as follows:
- Judges based only on the viewport: The actual space occupied by the component is unknown.
- Component reusability decreases: Placing the same component in different sized areas can make it look different than intended.
- Vulnerable to layout changes: If the parent layout changes, the style of the child component must also be modified.
These problems can be solved with container queries.
What are Container Queries?
Unlike media queries, container queries are a CSS feature that allows you to apply styles based on the size of the parent element (container) rather than the viewport. This allows you to create truly reusable components that respond to the space the component actually occupies.
Container queries can apply styles based on the state of the container, such as:
Size Queries
- Applies styles based on the width and height of the container.
- The most commonly used method.
Scroll State Queries
- Applies styles based on the scroll state of the container.
Simple Example
Let's apply container queries to the situation we first mentioned.
.card-container {
container-type: inline-size;
}
/* When the container is less than 400px: vertical layout */
.card {
display: flex;
flex-direction: column;
}
/* When the container is 400px or more: horizontal layout */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 180px 1fr;
}
}<div class="layout">
<main class="main-content">
<!-- Main area: wide container -->
<div class="card-container">
<div class="card">...</div>
</div>
</main>
<aside class="sidebar">
<!-- Sidebar: narrow container -->
<div class="card-container">
<div class="card">...</div>
</div>
</aside>
</div>Although it is the same component, styles are applied based on the size of the container, not the viewport, so it naturally appears as a horizontal layout in a wide area and a vertical layout in a narrow area.
Basic Usage
To use container queries, you must first specify the container type on the parent element that will serve as the reference using the container-type property.
/* Keyword values */
container-type: normal;
container-type: size;
container-type: inline-size;
container-type: scroll-state;
/* Two values */
container-type: size scroll-state;
/* Global Values */
container-type: inherit;
container-type: initial;
container-type: revert;
container-type: revert-layer;
container-type: unset;normal: Default value. Size queries are not possible, only style queries are.size: Based on the inline (horizontal) and block (vertical) direction sizes.inline-size: Based on the inline direction (generally width) size.scroll-state: Based on the scroll state.
Writing Queries with @container Rule
Once you've specified a container, you can define container queries using the @container at-rule. It uses a syntax similar to @media in media queries.
/* Basic Syntax */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 180px 1fr;
}
}
/* Max Width */
@container (max-width: 400px) {
.card {
flex-direction: column;
}
}
/* Range Specification */
@container (min-width: 400px) and (max-width: 800px) {
.card {
padding: 20px;
}
}Specifying Container Names
When multiple containers are nested, you can specify a particular container using the container-name property.
/* Give a name to the container */
.card-container {
container-type: inline-size;
container-name: card;
}
.sidebar-container {
container-type: inline-size;
container-name: sidebar;
}
/* Query targeting a specific container */
@container card (min-width: 400px) {
.card-title {
font-size: 1.5rem;
}
}
@container sidebar (min-width: 300px) {
.sidebar-content {
padding: 16px;
}
}If a container name is not specified, the query is applied based on the nearest parent container.
New Units
With the advent of container queries, new length units based on the size of the container have also appeared. Just as viewport units (vw, vh) are based on the viewport size, container query units are based on the size of the container.
Container Query Length Units
Basic Units
cqw: 1% of container width (container query width)cqh: 1% of container height (container query height)cqi: 1% of container inline-size (container query inline-size)cqb: 1% of container block-size (container query block-size)cqmin: The smaller value betweencqiandcqbcqmax: The larger value betweencqiandcqb
In most cases, in horizontal writing mode, cqi means width and cqb means height.
.card {
/* Padding increases as the container grows */
padding: 2cqi;
gap: 1cqi;
}Style Queries
Container queries can query not only the size but also the style values applied to the container. Style queries allow you to dynamically change the style of child elements based on the value of CSS custom properties (CSS variables).
Basic Usage
Style queries use the style() function to check the style values of a container.
/* Define CSS variable in container */
.card-container {
--theme: dark;
}
/* Query based on the style value of the container */
@container style(--theme: dark) {
.card {
background: #1a1a1a;
color: #ffffff;
}
}
@container style(--theme: light) {
.card {
background: #ffffff;
color: #000000;
}
}Wrap-up
Not only JavaScript, but CSS is also adding new features every year. Things that previously had to be implemented complexly with JavaScript can now be done with CSS alone. Therefore, it seems necessary to keep an eye on new CSS features as well.
Using container queries, you can create truly reusable components. It's much more convenient because the same component responds automatically to the space it occupies, wherever it is placed. It feels like problems that were difficult to solve with media queries are being solved cleanly.
However, CSS is difficult to use polyfills for, unlike JavaScript. Therefore, you must check browser support before actually applying it to a project. Fortunately, container queries are supported in major browsers, but scroll queries and style queries are still in experimental stages, so it's better to check target browsers and apply them progressively if necessary.
Reference