# Reusable Components Query Their Container

**SHOULD** · **ID:** `layout-container-queries` · **Category:** layout
**Source:** [Tailwind](https://tailwindcss.com/docs)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/layout-container-queries

> SHOULD: A reusable component must respond to ITS OWN width, not the viewport: `md:flex-row` asks about the browser window, so the card that reads right in the main column goes horizontal inside a 300px sidebar. Put `@container` on the wrapper and `@md:` on the children — now it asks "is my container ≥28rem", with no `isCompact` prop threaded through three layers. Container queries are core in v4; installing `@tailwindcss/container-queries` is a mistake, not a dependency. The `@` variants read the nearest `@container` ANCESTOR, so the wrapper must not be the element you are sizing.

Size a reusable component against its own container with @container and @md:, not against the viewport

A breakpoint variant like `md:flex-row` asks a question about the browser window, but a reusable `<Card>` does not live in the browser window — it lives in whatever box you dropped it into. So the card that looks right in the main column goes horizontal inside a 300px sidebar, because the *viewport* is 1400px wide and the card has no idea it is not. The component is not broken; the question it is asking is. Container queries let it ask the right one: put `@container` on the card's wrapper and the `@md:flex-row` on its child now means "when my container is at least 28rem", which is true in the main column and false in the sidebar, with no props, no context, and no `isCompact` boolean threaded through three layers. This is core in v4 — installing `@tailwindcss/container-queries` is now a mistake, not a dependency. Two things to keep straight: the `@` variants read the nearest `@container` ancestor, so the wrapper must be a different element from the one you are sizing; and named containers (`@container/sidebar` with `@md/sidebar:`) exist for when containers nest and you need to skip past the nearest one.

## Rule snippet

```tsx
<div class="@container">
  <div class="flex flex-col @md:flex-row">…</div>
</div>
```

## Bad — do not do this

`layout-container-queries-bad`

```tsx
/**
 * The card asks the viewport how much room it has. The viewport is wide, so the
 * card goes horizontal — even the copy sitting in a 240px rail.
 */
function Card() {
  return (
    <article className="flex flex-col sm:flex-row gap-3 rounded-lg border border-border bg-card p-3">
      <div className="size-12 shrink-0 rounded-md bg-muted" aria-hidden="true" />
      <div className="min-w-0">
        <h5 className="text-sm font-medium">Quarterly report</h5>
        <p className="text-xs text-muted-foreground">Updated 2 hours ago by Dana</p>
      </div>
    </article>
  );
}

export function ContainerQueriesBad() {
  return (
    <div className="w-full space-y-4">
      <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
        <pre className="text-error">{`<article class="flex flex-col sm:flex-row">  <!-- asks the window -->`}</pre>
      </div>

      <div className="space-y-4">
        <div>
          <p className="text-xs text-muted-foreground mb-2">In a 240px rail</p>
          <div className="w-60 rounded-lg border border-dashed border-border p-2">
            <Card />
          </div>
          <p className="text-xs text-error mt-2">
            Horizontal and crushed. The rail is 240px wide, but <code className="font-mono">sm:</code> only asked
            whether the <em>window</em> was 640px.
          </p>
        </div>

        <div>
          <p className="text-xs text-muted-foreground mb-2">In the main column</p>
          <div className="rounded-lg border border-dashed border-border p-2">
            <Card />
          </div>
          <p className="text-xs text-muted-foreground mt-2">Fine here — which is why nobody catches it.</p>
        </div>
      </div>

      <p className="text-xs text-error">
        The component is not broken. The question it is asking is: it queries the window it does not live in.
      </p>
    </div>
  );
}
```

## Good — do this

`layout-container-queries-good`

```tsx
/**
 * `@container` marks the wrapper as the thing to measure; `@xs:` on the child means
 * "when my container is at least 20rem". Same component, right answer in both slots.
 */
function Card() {
  return (
    <div className="@container">
      <article className="flex flex-col @xs:flex-row gap-3 rounded-lg border border-border bg-card p-3">
        <div className="size-12 shrink-0 rounded-md bg-muted" aria-hidden="true" />
        <div className="min-w-0">
          <h5 className="text-sm font-medium">Quarterly report</h5>
          <p className="text-xs text-muted-foreground">Updated 2 hours ago by Dana</p>
        </div>
      </article>
    </div>
  );
}

export function ContainerQueriesGood() {
  return (
    <div className="w-full space-y-4">
      <div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
        <pre className="text-foreground">{`<div class="@container">                          <!-- measure me -->
  <article class="flex flex-col @xs:flex-row">  <!-- ask my container -->`}</pre>
      </div>

      <div className="space-y-4">
        <div>
          <p className="text-xs text-muted-foreground mb-2">In a 240px rail</p>
          <div className="w-60 rounded-lg border border-dashed border-border p-2">
            <Card />
          </div>
          <p className="text-xs text-success mt-2">Stacks. Its container is under 20rem, and it knows.</p>
        </div>

        <div>
          <p className="text-xs text-muted-foreground mb-2">In the main column</p>
          <div className="rounded-lg border border-dashed border-border p-2">
            <Card />
          </div>
          <p className="text-xs text-success mt-2">Goes horizontal. Same component, no props, no context.</p>
        </div>
      </div>

      <p className="text-xs text-muted-foreground">
        Core in v4 — do not install <code className="font-mono">@tailwindcss/container-queries</code>. Name the
        container (<code className="font-mono">@container/rail</code>, <code className="font-mono">@xs/rail:</code>)
        when they nest.
      </p>
    </div>
  );
}
```

## References

- [Tailwind v4: Responsive design](https://tailwindcss.com/docs/responsive-design)
- [Tailwind CSS v4.0 announcement](https://tailwindcss.com/blog/tailwindcss-v4)
- [MDN: @container](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@container)
