Container queries

Style an element by the size of its container, not the viewport

Your browser does not support container-type, so the samples below fall back to their default layout.

Size queries

The card below switches from stacked to side-by-side at a container width of 28rem. Drag the resize handle in the bottom-right corner of the dashed box — the viewport never changes (see for more information).

A card that adapts

Same markup, same viewport — only the container width differs.

styles.css
.cq-resizable {
    container-type: inline-size;
    container-name: card;
}

.cq-card {
    display: grid;
    gap: 1rem;
}

@container card (min-width: 28rem) {
    .cq-card {
        grid-template-columns: 14rem 1fr;
        align-items: center;
    }
}

Container query units

cqi is 1% of the container’s inline size — the container-scoped answer to vw. Resize the box and the headline scales with it.

Fluid type with 8cqi

styles.css
.cq-fluid {
    container-type: inline-size;
}

.cq-fluid__headline {
    /* 8% of the container's inline size, not the viewport */
    font-size: clamp(1rem, 8cqi, 3rem);
}

Style queries

A container query can also match a custom property value. Both badges below share one class — only the --tone value on their container differs. Style queries need no container-type, but browser support is narrower than for size queries.

tone: info
tone: warn
styles.css
.cq-tone { --tone: info; }
.cq-tone[data-tone="warn"] { --tone: warn; }

.cq-badge {
    background: #dbeafe;
    color: #1e40af;
}

@container style(--tone: warn) {
    .cq-badge {
        background: #fef3c7;
        color: #92400e;
    }
}

Style queries with typed attr()

The grid below declares its whole configuration as data-* attributes. Typed attr() feeds two of them straight into grid-template-columns and gap, and turns the other two into custom properties the cards pick up with style queries. The controls only change attributes (adapted from utilitybend).

Your browser does not support typed attr(), so the grid keeps its default column size and gap. The accent and the hero card still follow the controls, through attribute selectors instead of attr().

Grid attributes
Accent-colorterracotta
Dining

Orange canteen

Dining

Canteen by the windows

Bar

Neon lounge

Details

Seating and floor tiles

Light

Daylight through the arches

markup.html
<div class="fluid-grid"
     data-min-size="220px"
     data-gap="2"
     data-featured="true"
     data-accent="terracotta">
  <article class="card">…</article>
</div>
styles.css
.fluid-grid {
    --featured: attr(data-featured type(<custom-ident>), false);
    --accent: attr(data-accent type(<custom-ident>), terracotta);

    display: grid;
    grid-template-columns: repeat(auto-fit,
        minmax(min(attr(data-min-size type(<length>), 220px), 100%), 1fr));
    gap: attr(data-gap cqi, 2cqi);
}

@container style(--featured: true) {
    .card:first-child {
        grid-column: span 2;
        grid-row: span 2;
    }
}

@container style(--accent: sage) {
    .card { --accent-color: oklch(48% 0.11 150); }
}

.card {
    --card-accent: var(--accent-color, oklch(52% 0.16 35));
    border-top: 3px solid var(--card-accent);
}

Gotchas