# Container Queries: Components That Fit Their Space

A container query changes a component's style by the width of the box it sits in instead of the width of the screen, so one product card can be stacked in a narrow sidebar and side by side in a wide spot on the same page.

Freelancer · CSS · Updated November 1, 2026

## After this lesson you can

- Make an element a container and write an @container rule that restyles what's inside it
- Work out which @container rules apply to a component for its container's width
- Choose between a media query, a container query and clamp() for a responsive change

## Key takeaways

- A media query asks how wide the screen is. A container query asks how wide the component's own box is.
- Make a box a container with `container-type: inline-size`, then restyle what's inside it with a rule such as `@container (min-width: 30rem)`.
- A container query styles the elements inside the container, never the container itself, so the container is usually a wrapper.
- container-name lets a query say which container it means, and a cqi unit is 1% of the container's width.
- Media queries still fit the page's own layout and the reader's settings. Container queries fit components that move around.

Spoke & Chain's catalog page shows the same bike card in three places: in a narrow "Staff pick" sidebar, in a three-column grid of bikes, and in a wide "Bike of the month" spot at the top. In the wide spot, the card looks best side by side, with the photo on the left and the words on the right. In the sidebar, it has to stack, or the photo shrinks to a sliver. A media query can't tell those two apart: they're on the same screen. Any site that reuses a piece of the page in different places (a shop's product cards, a news site's story previews, a music app's album tiles) runs into this, and container queries are the answer.

## The problem with asking the screen

Here is how the card was first made side by side, with a media query:

```css
.bike-card {
  display: grid;
  gap: 1rem;
}

@media (min-width: 60rem) {
  .bike-card {
    grid-template-columns: 2fr 3fr;
  }
}
```

On a screen 60rem wide or more, every card goes side by side. The bike of the month looks great. But the staff pick, squeezed into a sidebar 18rem wide, goes side by side too: a photo the width of a thumb next to a column of words broken one per line. The media query asked the right question about the wrong thing. It asked how wide the **screen** is, when what matters is how wide the **card's own space** is.

![The same Spoke & Chain page on a wide screen, with a media query: the bike-of-the-month card side by side and looking good, and the sidebar's card also side by side, its photo crushed and its title broken into single words](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/10/85ff9413187c40a9805bf2aee153a9d2.webp)

## Asking the box instead: container-type

Think of a sofa bed. It doesn't fold out because the house is big; it folds out when the room it's in has space for it. The same sofa bed sits folded in a small study and opens up in a big living room.

A **container query** (read "container query") is that idea in CSS: a rule that checks the size of the box a component sits in. First you tell the browser which box to measure, with **`container-type`**:

```css
.card-spot {
  container-type: inline-size;
}
```

That makes every `.card-spot` a **query container**: a box whose width the elements inside it can ask about. **`inline-size`** means the size along the line of text, which for English, running left to right, is the width. (There's also `size`, which measures the height as well, but it only works on a box with a set height, so `inline-size` is the one you'll use nearly every time.)

One thing changes when a box becomes a container: it can no longer take its width from what's inside it. In a grid cell, a sidebar or a section, that's fine, because those already get their width from the page. But a container that sits in a flex row with no width of its own can shrink to nothing. If a container seems to vanish, give it a width or let it fill its space.

## Writing the query: @container

With a container in place, **`@container`** (read "at container") asks about it, in the same shape as a media query:

```css
@container (min-width: 30rem) {
  .bike-card {
    grid-template-columns: 2fr 3fr;
    align-items: center;
  }
}
```

Read it aloud: "when the container is at least 30rem wide, put the card's photo and words in two columns". The browser measures the nearest container around each card: the bike-of-the-month spot, about 45rem wide, matches, and its card goes side by side. The sidebar's spot, 18rem wide, doesn't, so its card stays stacked. Same card, same page, two looks. The newer range form works here too: `@container (width >= 30rem)`, read "width at least 30rem".

There's one rule to remember: **a container query styles what's inside the container, never the container itself.** A box can't change based on its own width, because changing it could change that width. That's why the card sits inside a wrapper:

```html
<div class="card-spot">
  <article class="bike-card">
    <img class="bike-card-photo" src="images/gravel-fox.svg" alt="The Gravel Fox, an orange gravel bike with wide tires">
    <div class="bike-card-body">
      <h3 class="bike-card-title">Gravel Fox</h3>
      <p class="bike-card-price">$1,180</p>
    </div>
  </article>
</div>
```

The `.card-spot` is measured, and the `.bike-card` inside it changes.

## Naming containers

A page can have containers inside containers: a card spot inside a sidebar that's a container too. Without a name, `@container` measures the **nearest** container around the element. A **container name** lets the query say exactly which one it means:

```css
.card-spot {
  container-type: inline-size;
  container-name: bike-spot;
}

@container bike-spot (min-width: 30rem) {
  .bike-card {
    grid-template-columns: 2fr 3fr;
  }
}
```

Now the rule only ever measures a container named `bike-spot`, wherever the card ends up. The name is yours to choose. Many people write both lines at once with the `container` shorthand: `container: bike-spot / inline-size;`, read "named bike-spot, measuring the inline size". The name comes first, then a slash, then the type.

Container rules follow the same cascade as everything else. If you write a second rule for wider spots, such as `@container bike-spot (min-width: 45rem)`, put it **after** the 30rem one: in a 50rem spot both match, and the later one wins where they disagree. That's mobile first again, for a component: small first, then more room.

## Sizes measured from the container: cqi

You've met `vw`, 1% of the window's width. A **container query unit** does the same for the container: **`1cqi`** (read "container query inline") is 1% of the container's inline size, its width.

```css
.bike-card-title {
  font-size: clamp(1.125rem, 0.75rem + 3cqi, 2rem);
}
```

It's a fluid heading, as in *Fluid Type and Spacing with clamp()*, but it grows with the card's space instead of the window. In the 18rem sidebar spot (288 pixels), the title is 12 + 8.64 = 20.64 pixels. In a 45rem bike-of-the-month spot (720 pixels), 12 + 21.6 = 33.6 pixels is more than the maximum, so it's 32. The rem part keeps zoom working, as before. There's a `cqw` unit too (container width); `cqi` is the one that matches `inline-size`.

**Try it.** Make a file `cards.html` with two `.card-spot` wrappers, one `style="width: 18rem"` and one `style="width: 45rem"`, each holding the same card. Add the rules from this lesson. The two cards should look different, side by side on the same page. Then inspect a card spot: the Elements panel shows a **container** badge beside it.

## Media query, container query or clamp()?

Container queries don't replace media queries; they answer a different question. A short way to choose:

| The change depends on… | Use | For example |
|---|---|---|
| the whole page's width, for the page's own layout | a media query | the sidebar moves beside the main area at 60rem |
| the reader or their device | a media query | dark mode with `prefers-color-scheme`, less motion with `prefers-reduced-motion`, `hover` |
| the space one component has | a container query | a card that's stacked in a sidebar and side by side in a wide spot |
| nothing switching: a size that should grow smoothly | `clamp()` | a heading that grows from 28 to 48 pixels |

Many pages use all three: a media query for the page layout, container queries for the cards inside it, and `clamp()` for the headings.

Container size queries (`container-type`, `container-name`, `@container` and the `cqi` units) work in every current browser: Chrome and Edge since version 105, Safari since 16 and Firefox since 110, and they've been **Baseline widely available** since August 2025 (checked on web.dev in October 2026). You can use them on any site you build today.

## Check yourself

3 questions to try on the lesson page.

## So what

A component that adapts to its own space can go anywhere: a sidebar, a grid, a feature spot, a page that doesn't exist yet. That's how big sites keep hundreds of pages consistent, by building each piece once and letting it fit wherever it's placed.

## Key terms

- [Container query](https://www.ask-codey.com/glossary/container-query)
- [@container](https://www.ask-codey.com/glossary/container)
- [Container type](https://www.ask-codey.com/glossary/container-type)
- [Container name](https://www.ask-codey.com/glossary/container-name)
- [Container query units](https://www.ask-codey.com/glossary/container-query-units)
- [Query container](https://www.ask-codey.com/glossary/query-container)

## Cheat sheets

- [Container Queries: Components That Fit Their Space](https://www.ask-codey.com/cheat-sheets/css-container-queries-cheat-sheet)

## Related

- [Media Queries and Breakpoints](https://www.ask-codey.com/lessons/css-media-queries)
- [Fluid Type and Spacing with clamp()](https://www.ask-codey.com/lessons/css-clamp-fluid-type)
- [Exercise: Bike Cards That Fit Anywhere](https://www.ask-codey.com/lessons/exercise-css-bike-shop-container-cards)

Source: https://www.ask-codey.com/lessons/css-container-queries
