Back to blog
Article

Container queries for widgets that appear in three widths

Container queries for widgets that appear in three widths
S

StriveBit

3 min readWeb Applications

A widget that appears in three places shouldn't know the screen width

We ship an order-management widget — a compact table of recent orders with a status pill, a customer name, a timestamp, and a line-item count — into three spots in the same Next.js admin panel. It sits full-width on the orders page, at 50% width in a two-column dashboard, and at roughly 320px inside a slide-over drawer for the customer detail view. On a mid-range Android phone over a patchy connection, that last container might render at 280px because the drawer doesn't take the full viewport.

Viewport media queries break this. A `@media (max-width: 640px)` rule fires based on the phone's screen, not the widget's actual available width. So the widget gets the mobile layout when the phone is narrow, even when it's sitting in a full-width orders page that has plenty of room. And it gets the desktop layout inside the drawer even when the drawer is only 300px wide. You end up writing JS to measure the parent's width and toggle class names — a resize observer, a state variable, a re-render — for something the browser can do natively.

The fix is a container query. The widget declares its nearest ancestor as a containment context, then reacts to that ancestor's width.

.widget-shell {
  container-type: inline-size;
  container-name: panel;
}

@container panel (min-width: 480px) {
  .order-row {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    gap: 1rem;
  }
}

@container panel (max-width: 320px) {
  .order-row .item-count {
    display: none;
  }
}

The `container-type: inline-size` declaration tells the browser to track the inline (horizontal) dimension of `.widget-shell`. Inside `@container`, the rules apply based on that element's resolved width, not the viewport. No JavaScript, no resize observer, no re-render.

The tradeoff is real. A container query locks you out of `min-width` / `max-width` on the container itself for normal CSS — `container-type: inline-size` establishes size containment, which means the element's own size is no longer influenced by its children. That's fine for a widget shell whose width is set by its parent grid or flex layout, but it's a problem if you were expecting the element to shrink-wrap around content. You also need at least one ancestor with a defined size; container queries don't work if every ancestor is `width: auto` all the way up. In practice the layout grid handles this.

Browser support for container queries landed in all evergreen browsers by early 2023 — Chrome 105, Safari 16, Firefox 110. The admin panels we build target Chrome on Android and Safari on iOS, so there's no fallback needed. For the rare client still supporting a legacy Android WebView, the `@container` rules simply don't apply and the widget falls back to its base styles, which are usable if a little wide.

The concrete win is layout count. Before container queries, we maintained three sets of breakpoints for the same widget — one for the orders page, one for the dashboard, one for the drawer — all keyed to the viewport but with different target widths. When the designer changed the drawer from 360px to 320px, two of the three broke. Now there's one set of `@container` rules, keyed to the actual widths the widget encounters, and they hold across all three placements.

Back to all articles

Ready to build something great?

We help ambitious teams build software that lasts. If you're interested in working with us or want to discuss your project, let's connect.

Get in touch