DEV Community

Cover image for Container queries can check more than size — style queries let components read custom property values
Parsa Jiravand
Parsa Jiravand

Posted on

Container queries can check more than size — style queries let components read custom property values

When a component needs to know its context — is it inside a dark sidebar? a compact layout? a danger banner? — the usual answer is JavaScript. You pass a prop, toggle a class, and the styles follow. But the context you're checking is often already sitting in a CSS custom property on some ancestor element. Style queries let the component read it directly, with no JavaScript in the middle.

The size queries you already know

If you've used container queries, you know the size query pattern:

.card { container-type: inline-size; }

@container (min-width: 400px) {
  .card-body { display: grid; grid-template-columns: 1fr 1fr; }
}
Enter fullscreen mode Exit fullscreen mode

The child adapts when the container crosses a width threshold. Style queries use the same @container syntax, but check a CSS custom property value instead of a size.

What a style query looks like

.sidebar {
  container-type: inline-size;
  --theme: dark;
}

@container style(--theme: dark) {
  .button {
    background: #1e293b;
    color: #f8fafc;
  }
}
Enter fullscreen mode Exit fullscreen mode

The .button inside .sidebar asks: does my nearest container ancestor have --theme set to dark? If yes, apply these styles. No JavaScript. No extra class on the button. The context lives in the custom property; the component reads it.

The mental model: CSS custom properties already flow down

Custom properties cascade from parent to child — you've always been able to use --theme inside a descendant. What you couldn't do was branch on its value in CSS alone. A style query adds that branch.

/* Intent set once on the layout root */
.layout[data-mode="compact"]  { --density: compact; }
.layout[data-mode="spacious"] { --density: spacious; }

.panel { container-type: inline-size; }

/* Any descendant component reads the inherited context */
@container style(--density: compact) {
  .list-item { padding: 4px 8px; font-size: 0.875rem; }
}

@container style(--density: spacious) {
  .list-item { padding: 12px 16px; font-size: 1rem; }
}
Enter fullscreen mode Exit fullscreen mode

The data attribute on .layout sets the intent once. The custom property carries it down the tree. The style query acts on it wherever the component appears — the component itself needs no extra class, no prop, no knowledge of where it's placed.

A real-world use case: variant-aware alerts

Component variant systems are a natural fit. A severity-aware alert where every sub-element adapts:

.alert {
  container-type: inline-size;
  --alert-severity: neutral;
}

.alert[data-severity="error"]   { --alert-severity: error; }
.alert[data-severity="warning"] { --alert-severity: warning; }
.alert[data-severity="success"] { --alert-severity: success; }

@container style(--alert-severity: error) {
  .alert-icon { color: var(--red-600); }
  .alert-bar  { background: var(--red-100); border-color: var(--red-600); }
}

@container style(--alert-severity: warning) {
  .alert-icon { color: var(--amber-600); }
  .alert-bar  { background: var(--amber-100); border-color: var(--amber-600); }
}

@container style(--alert-severity: success) {
  .alert-icon { color: var(--green-600); }
  .alert-bar  { background: var(--green-100); border-color: var(--green-600); }
}
Enter fullscreen mode Exit fullscreen mode

The severity is declared once on .alert via a data attribute. Every nested element — the icon, the border bar, a close button you add later — reads it through the style query. Adding a new sub-element means one new @container style(...) rule; nothing else changes.

Combining size and style in one block

Style and size queries compose. A single @container block can check both:

@container card (max-width: 300px) and style(--theme: dark) {
  .card-label {
    font-size: 0.75rem;
    color: var(--dark-muted);
  }
}
Enter fullscreen mode Exit fullscreen mode

This applies only when the container named card is narrow and has --theme: dark. The combination lets you encode conditional behavior in CSS that would otherwise require a computed class name or inline style injection.

One thing to remember about the container

The container-type declaration is still needed on the ancestor for style queries to work — it establishes the containment context that @container style(...) queries against. If you only need style queries and not size queries, you can use container-type: normal (or just set container-name without a type). Without any container declaration on the ancestor, the style query has nothing to inspect and applies nothing.

/* Size + style queries: use inline-size or size */
.card { container-type: inline-size; }

/* Style queries only — no size containment needed */
.theme-root { container-name: themed; }

@container themed style(--theme: dark) { ... }
Enter fullscreen mode Exit fullscreen mode

Browser support

Style queries are Baseline 2024: Chrome 111 (March 2023), Firefox 110 (February 2023), Safari 17.2 (December 2023). All three major engines shipped within the same year. If your project already uses container size queries, style queries are available in the same browsers.

🎮 Try it yourself

▶️ Open the interactive playground →

Runs right in your browser — poke at it and watch the concept react live.

🧠 Test yourself

Think it clicked? Take the 9-question quiz →

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

The takeaway

Scan your codebase for places where JavaScript reads a state value and then toggles a CSS class to change a component's appearance. If that state is already encoded as a CSS custom property on an ancestor element, a style query can replace the JavaScript bridge entirely. The property carries the context down the tree; @container style(...) branches on it wherever the component appears. The component stays self-contained — it reads its environment rather than getting configuration pushed in from outside.


Thanks for reading! Let's stay connected:

Top comments (0)