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.
How container queries work
Section titled “How container queries work”- 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(mdis 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.
Set up a container
Section titled “Set up a container”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>.sidebar { @include container-type; @include container-name(sidebar);}| 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.
Write a query
Section titled “Write a query”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; }}Mixins
Section titled “Mixins”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.
Custom container names
Section titled “Custom container names”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; }}Breakpoints
Section titled “Breakpoints”The mixins resolve breakpoint names from $breakpoints. See Media queries to change the map; the container query mixins follow it.
| Suffix | Media 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.
Configuration
Section titled “Configuration”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.
Errors
Section titled “Errors”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; }}Class reference
Section titled “Class reference”Generated from the compiled core build. The classes have no responsive or state variants.
Query containers (6 rules)
| Selector | Declarations | Default value |
|---|---|---|
.container-inline-size | container-type: inline-size | inline-size |
.container-size | container-type: size | size |
.container-main | container-name: main | main |
.container-sidebar | container-name: sidebar | sidebar |
.container-card | container-name: card | card |
.container-section | container-name: section | section |
Accessibility and RTL
Section titled “Accessibility and RTL”- Containment has side effects.
container-type: inline-sizeapplies 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.