How CSS Container Queries Work in Safari
CSS Container Queries, supported natively starting in Safari 16, fundamentally change responsive web design by allowing components to adapt their styles based on the size of their parent container rather than the entire browser viewport. This article explains the mechanics behind this feature in Safari's WebKit engine, covering how to establish a containment context, write container query rules, and utilize container-relative units to create modular, reusable UI components that look great regardless of where they are placed on a page.
The Shift from Viewport to Parent Container
Traditionally, responsive design relied on CSS Media Queries
(@media), which apply styles based on the dimensions of the
entire screen or browser window. However, a component might be placed in
a narrow sidebar on one page and a wide main content area on another.
Media queries cannot easily account for this because the viewport size
remains the same in both scenarios.
Safari solves this with Container Queries. Instead of asking the browser how wide the screen is, a component can ask how wide its immediate parent (or a specific ancestor) container is, and adjust its layout, typography, and visibility accordingly.
Step 1: Establishing a Containment Context
For Safari to adapt a component based on its parent, you must first tell the browser which element acts as the container. By default, HTML elements do not track their own dimensions for query purposes because doing so for every element would slow down page rendering.
You establish a container using the container-type
property on the parent element:
.card-wrapper {
container-type: inline-size;
container-name: card-container;
}Safari supports three primary values for
container-type:
inline-size: Tracks the width of the container (in standard horizontal writing modes). This is the most commonly used value.size: Tracks both the width and the height of the container.normal: The default value; the element is not a query container for dimensions.
The optional container-name property allows you to name
the container, which is useful if a component is nested inside multiple
containers and you want to target a specific ancestor. You can also use
the shorthand property
container: card-container / inline-size;.
Step 2: Applying the
@container Rule
Once the parent has a defined container-type, Safari’s
WebKit engine monitors that element's dimensions. You can then use the
@container rule to style any child elements inside it.
The syntax mirrors standard media queries:
/* Default styles for narrow containers */
.card {
display: flex;
flex-direction: column;
}
/* Styles applied when the parent container is at least 500px wide */
@container card-container (min-width: 500px) {
.card {
flex-direction: row;
gap: 20px;
}
}When Safari renders the page, it calculates the width of
.card-wrapper. If that specific element is 500 pixels or
wider, the .card inside it switches from a vertical column
layout to a horizontal row layout. If you have two
.card-wrapper elements on the same page—one at 300 pixels
wide and one at 600 pixels wide—Safari will simultaneously render the
narrow card as a column and the wide card as a row.
Step 3: Utilizing Container Query Units
In addition to layout changes via the @container rule,
Safari supports CSS container query length units. These allow elements
to scale fluidly based on the parent's dimensions without writing
multiple query breakpoints.
The most common units include:
cqw: 1% of a query container's width.cqh: 1% of a query container's height.cqi: 1% of a query container's inline size (width in horizontal layouts).cqb: 1% of a query container's block size (height in horizontal layouts).
For example, to make a heading's font size scale dynamically with the width of its parent container rather than the viewport, you write:
.card-title {
font-size: clamp(1rem, 4cqi, 2.5rem);
}How Safari Prevents Infinite Loops
A major technical reason Container Queries took years to arrive in browsers like Safari was the risk of infinite styling loops. If a parent container shrinks to fit its child content, and a container query makes the child content larger when the parent is small, the parent would expand, triggering the query to shrink the child, causing the parent to shrink again infinitely.
Safari's WebKit engine prevents this by enforcing size
containment when you apply container-type. When
you set container-type: inline-size, Safari calculates the
width of the parent container entirely independently of the size of its
children. Because the children cannot influence the width of the parent,
the browser can safely apply styles to the children based on the
parent's width without ever triggering a circular layout
calculation.