Skip to content

Container queries

Container queries style an element by the width of its container instead of the viewport. A card in a narrow sidebar and the same card in a wide main column can then lay out differently from one stylesheet. Core provides classes that turn an element into a query container, and mixins that write the queries with the same breakpoint names as media queries.

  • Two parts. An ancestor becomes a query container (container-type), and a descendant writes a query against it (@container). An element cannot query itself.
  • Nearest or named. A query without a name targets the nearest query container. A query with a name targets the nearest ancestor that has that container-name.
  • Same breakpoints. The mixins accept the names from $breakpoints (md is 768px) or a length. Upper bounds end 0.02px below a breakpoint, as in the media query mixins.
  • Mixins only. There are no container-responsive utility classes: the queries are written in SCSS with the mixins below.

Add container-inline-size to the element whose width should be queried. Add a name class when a query must target this container and not a nearer one.

<aside class="container-inline-size container-sidebar p-3 bg-alternate rounded">
<div class="p-3 bg-surface border rounded">
This element can query the width of the aside.
</div>
</aside>
Class Declaration
container-inline-size container-type: inline-size
container-size container-type: size
container-main container-name: main
container-sidebar container-name: sidebar
container-card container-name: card
container-section container-name: section

Use container-inline-size in almost every case: it measures the width only, and the container keeps its normal height from its content. container-size also measures the height, which requires the container to have an explicit height; without one it collapses to zero.

These classes are not related to the page container (.container), which sets a centred maximum width. An element can have both.

A descendant of the container uses a query mixin. The query below applies when the sidebar container is at least 640px (sm) wide.

<aside class="container-inline-size container-sidebar">
<div class="profile-card">...</div>
</aside>
.profile-card {
@include apply(flex, col, gap(2));
@include container-up(sm, sidebar) {
@include apply(row, x-center);
}
}
.profile-card {
display: flex;
flex-direction: column;
gap: var(--nu-space-2);
}
.profile-card > * {
--nu-flex-gap: var(--nu-space-2);
}
@container sidebar (min-width: 640px) {
.profile-card {
flex-direction: row;
align-items: center;
}
}

Every query mixin takes an optional container name as its last argument. Without it, the query targets the nearest query container.

Mixin Applies Example output
container-up($breakpoint, $containerName: null) Container at least the breakpoint width container-up(md, sidebar): @container sidebar (min-width: 768px)
container-down($breakpoint, $containerName: null) Container narrower than the breakpoint container-down(sm): @container (max-width: 639.98px)
container-between($lower, $upper, $containerName: null) From $lower up to just below $upper container-between(sm, lg, main): @container main (min-width: 640px) and (max-width: 1023.98px)
container-only($breakpoint, $containerName: null) From the breakpoint up to just below the next larger one container-only(md, card): @container card (min-width: 768px) and (max-width: 1023.98px)
container-query($size, $containerName: null) Container at least $size, used as written container-query(400px, sidebar): @container sidebar (min-width: 400px)
container-type($type: inline-size) Makes the element a query container container-type: inline-size
container-name($name) Names the container (quotes are removed) container-name: sidebar
.card-actions {
@include container-down(sm) {
flex-direction: column;
}
}
.card-media {
@include container-only(md, card) {
aspect-ratio: 4/3;
}
}
.widget {
@include container-query(400px, sidebar) {
display: grid;
}
}
@container (max-width: 639.98px) {
.card-actions {
flex-direction: column;
}
}
@container card (min-width: 768px) and (max-width: 1023.98px) {
.card-media {
aspect-ratio: 4/3;
}
}
@container sidebar (min-width: 400px) {
.widget {
display: grid;
}
}

The breakpoint names are viewport sizes, so the larger ones are rarely useful for a component. A length such as container-up(400px) or container-query(24rem) usually describes a component better than container-up(lg).

container-query() does not look up breakpoint names or check its argument: always pass a length. container-query(30) compiles to @container (min-width: 30), which browsers ignore.

container-name() accepts any name, so you are not limited to the four name classes.

.checkout-summary {
@include container-type;
@include container-name(summary);
}
.checkout-summary__total {
@include container-up(400px, summary) {
font-size: 1.25rem;
}
}

The mixins resolve breakpoint names from $breakpoints. See Media queries to change the map; the container query mixins follow it.

SuffixMedia query
@xs@media (min-width: 480px)
@sm@media (min-width: 640px)
@md@media (min-width: 768px)
@lg@media (min-width: 1024px)
@xl@media (min-width: 1280px)
@2xl@media (min-width: 1536px)

The table shows media queries for the responsive suffixes; a container query with the same name uses the same width.

The container-queries feature flag controls the classes on this page. The classes are generated only when $generate-utility-classes is true. The mixins are always available.

@use "@nuvoui/core" with (
$generate-utility-classes: true,
$feature-flags: ("container-queries": false)
);

The name classes come from $container-names (main, sidebar, card, section). This setting is not forwarded by the package entry point, so the four names are fixed; use container-name() in SCSS for any other name.

The breakpoint mixins fail at compile time for an unknown name or a unitless number.

Input Error
container-up(3) Invalid breakpoint: 3 is not a breakpoint name (xs, sm, md, lg, xl, 2xl). Write a unit for a custom width (3px).
container-up(huge) Invalid breakpoint: huge. Use a breakpoint name (xs, sm, md, lg, xl, 2xl) or a length such as 800px.
container-down(50%) Invalid breakpoint: 50%. Ranges that end below a breakpoint need a px, em or rem width.
// error
.card-title {
@include container-up(3) {
font-size: 1.25rem;
}
}

Generated from the compiled core build. The classes have no responsive or state variants.

Query containers (6 rules)
SelectorDeclarationsDefault value
.container-inline-sizecontainer-type: inline-sizeinline-size
.container-sizecontainer-type: sizesize
.container-maincontainer-name: mainmain
.container-sidebarcontainer-name: sidebarsidebar
.container-cardcontainer-name: cardcard
.container-sectioncontainer-name: sectionsection
  • Containment has side effects. container-type: inline-size applies inline size containment: the container no longer takes its width from its content. Give it a width from its parent (a block element, a grid or flex track) because inside a shrink-to-fit context, such as an inline-block or a flex item that does not grow, it can collapse to zero width.
  • Reflow. Container queries help content reflow in narrow regions and at high zoom levels without extra viewport breakpoints (WCAG 1.4.10).
  • Direction independent. A width query measures the container, not a side, so the same query applies in left-to-right and right-to-left pages.