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.