---
name: container-queries
description: "Audits and fixes CSS container queries that never match, collapse or fall back to the viewport: container-name with no container-type, a query that targets the container itself, size containers with no height, inline-size containers in shrink-to-fit boxes, unusable display values and unknown names. Runs a read-only checker, then converts component breakpoints from media queries in approved batches and measures two container widths. Use for @container not working or component-level responsive design. Do NOT use for page breakpoints, layout overflow, cascade conflicts or fluid type."
license: CC-BY-4.0
metadata:
  author: Afeez Adebayo
  author_url: https://afeezadebayo.com
  author_linkedin: https://www.linkedin.com/in/afeez-adebayo/
  author_x: https://x.com/za_icon
  source: https://afeezadebayo.com/skills-md/frontend-development/css-layout/container-queries/
  category: frontend-development
  subcategory: css-layout
  version: "1.0.0"
  created: "2026-10-25"
  updated: "2026-10-25"
---

# Container Queries: Make a Component Respond to Its Container, Not the Viewport

An agent works on container queries in this order: run a read-only checker over the stylesheets, find which ancestor is the container and how wide it is, decide whether a component should use a container query or stay on a media query, declare the container, write the queries, then measure the result at two container widths in a browser.

It works on plain CSS, bundler output and `<style>` blocks. Facts and measurements sit in `references/appendix.md`, component recipes in `references/patterns.md`.

## When to use this skill, and when not to

Use it when an `@container` rule does nothing, when a component collapses to zero width or height after `container-type` was added, when `cqi` units give viewport-sized values, or when a card, widget or sidebar item should change layout with the space it is given.

| Request | Use instead |
|---|---|
| Horizontal overflow, `1fr` blowouts, truncation | `flexbox-grid-layout.md` |
| A rule that loses to another rule | `cascade-layers.md` |
| Page-level breakpoints, orientation, `prefers-*` queries | Keep `@media`. Step 3 explains the split |
| `style()` and `scroll-state()` container queries | Out of scope for this release. They were not tested |
| Fluid type and spacing scales | Out of scope for this release |

## Guardrails

1. **Propose, then change.** Report and plan first. Edit only after the user approves a batch, on a branch under version control. Never push, merge or deploy.
2. **Never edit vendor files.** Do not change files in `node_modules`, vendor folders or CDN copies. Override them from your own CSS.
3. **Local pages only, no page text.** Measure only pages served from the user's machine, with fake data. The snippets read computed styles and sizes, never page text, cookies or storage. Do not paste `url(...)` values into reports.
4. **Batches stay small.** At most 1 component or 20 rules per batch (defaults), each shown as a diff.
5. **Keep what works.** Add the container query beside the old media query, then delete the media rule only after step 7 passes.
6. **Nothing is invented.** Thresholds are labelled defaults. Source facts carry a date. Version-dependent facts carry **verify before relying on this**. A breakpoint number comes from the component's content or the design, never from the old viewport value.
7. **Refuses to:** put `container-type: size` on an element with no height, make every wrapper a container, claim a fix without measuring two container widths, or guess a breakpoint.

## Inputs the agent must have before starting

Ask for every missing item. Do not assume.

| Input | Ask exactly |
|---|---|
| Stylesheets and markup | "List every stylesheet and the HTML or template that holds the component, in load order." |
| The component | "Which component should respond to its container? Where is it placed, and how wide is each slot?" |
| The layout change | "What should change, and at what width does the component stop fitting?" |
| Current behaviour | "Which `@media` rules does it use today?" |
| Browsers | "Which browsers must work? Container queries became Baseline newly available in 2023." |
| How to run the page | "How do I open it locally?" |
| Approval | "May I edit on a branch in batches, showing each diff first?" |

## Workflow

### Step 0: Confirm scope

List the files and the one component. Minified input is fine for the checker, but edits go in source files. One component at a time.

### Step 1: Run the checker

```
python3 scripts/cq_check.py page.css | head -40
```

Read only its output. Do not read the script. Pass every stylesheet that declares or uses containers, because a query is matched against containers in all the files you pass. Add `--strict` to exit 1 on any warning. The last two lines give counts. Each code has one fix, in this table.

| Code | Level | Meaning | Fix |
|---|---|---|---|
| `name-no-type` | warn | A container name with no `container-type` never matches a size query | `container: name / inline-size` |
| `query-unknown-name` | warn | `@container name` where no scanned file declares that name | Declare it, or fix the spelling, or pass the missing file |
| `query-no-container` | warn | An unnamed `@container` and no container-type anywhere | Declare a container on an ancestor |
| `self-style` | warn | A query styles the element that is the container | Style a child, or wrap the element |
| `size-no-height` | warn | `container-type: size` with no height, block-size or aspect-ratio collapses to 0 high | Use `inline-size`, or give it a height |
| `shrink-to-fit` | warn | An inline-size container in a float, inline-block or fit-content box collapses to 0 wide | Give it a width, `flex-basis` or `width: 100%` |
| `bad-display` | warn | Container-type on `display: inline`, `contents`, `table` or `table-cell` does not make a container | Use a block, flex, grid or inline-block box |
| `height-query-inline-size` | warn | A height query on an inline-size container never matches | Use `container-type: size` and a height |
| `cq-unit-no-container` | info | `cqi`, `cqw` and similar units with no container fall back to small viewport units | Declare a container, or use another unit |

### Step 2: Find the container in the browser

Run the ancestors snippet (Templates) on the element that should change. It lists every ancestor with a container type or name, with its type and size. If the list is empty, no query can match. If the nearest entry is not the one you expect, an unnamed query is matching the wrong container: name the query.

A query is measured on the container's content box. Padding does not count (Chromium 153: a 400px box with 50px padding each side matched `min-width: 300px` and failed `min-width: 350px`).

### Step 3: Decide container query or media query

| Finding | Decision |
|---|---|
| The component sits in slots of different widths, such as a main column and a sidebar | Container query |
| The layout depends on the viewport, such as the page grid, the header or `prefers-*` | Keep `@media` |
| A page shell contains components | `@media` for the shell, container queries inside the components |
| The component appears in one slot only and that slot never changes | Keep `@media`. A container adds a wrapper for no gain |

### Step 4: Declare the container

```css
.card-wrap { container: card / inline-size; }
```

| Rule | Reason |
|---|---|
| Put the container on a wrapper or a parent, never on the element you restyle | An element cannot be styled by a query on itself (measured, Chromium 153). Its `::before` and `::after` can |
| Always name it | An unnamed query matches the nearest container, which may be an inner one you did not mean (measured: a 200px inner container beat an 800px outer one) |
| Write the type | `container: card` alone sets a name and no type, so queries never match (measured) |
| Default to `inline-size` | `size` also applies size containment to height, so the box collapses unless it has a height (measured: 0px high) |
| Use a block, flex, grid or inline-block box | Containers on `display: inline`, `contents`, `table` and `table-cell` did not match (measured) |
| Give the container a width | In a float, an inline-block or an auto-width flex item, an inline-size container collapsed to 0px wide. `width: 100%`, `flex: 1` and `flex: 0 0 200px` all fixed it (measured) |

Use `size` only when you query height, and then give the container a `height`, `block-size` or `aspect-ratio`.

### Step 5: Write the queries

```css
.card { display: flex; flex-direction: column; gap: 1rem; }
@container card (width >= 400px) { .card { flex-direction: row; } }
```

| Rule | Reason |
|---|---|
| Base styles are the narrow layout, queries add the wide one | Mobile-first, fewer overrides |
| `(min-width: 400px)` and `(width >= 400px)` both work | Measured in Chromium 153. The bound is inclusive: 400px matched |
| Take the breakpoint from the content, not from the old viewport query | Ask the user, or measure the width at which the component stops fitting |
| Queries can sit inside `@media` and inside `@supports` | Measured |
| Keep every queried rule on a child of the container | Step 4 |

If browsers older than the Baseline 2023 versions must work, wrap the container declaration and the queries in `@supports (container-type: inline-size)` and keep the narrow layout as the base (measured: the support test is true in Chromium 153; **verify before relying on this** in older engines).

### Step 6: Container units

`cqi` is 1% of the container's inline size and `cqw` 1% of its width. Without a container they fall back to the small viewport units (MDN and web.dev notes; measured: `50cqw` gave 500px in a 1000px viewport, which cannot tell small from large viewport units). Use them for sizes that should follow the slot, wrapped in `clamp()` so a very narrow or very wide container cannot make them extreme.

### Step 7: Verify

1. Run the checker again. Warnings must be 0, or each is listed with a reason.
2. Run the measuring snippet (Templates) on the wrapper at one width below and one at the breakpoint. The computed value must change at the breakpoint and not before.
3. Check the component in every real slot, not only the test page.
4. Run the existing test suite if there is one.
5. List what you could not test: browsers older than your target, slots behind a login.

### Step 8: Report

Use the report template. A short list of file, line, issue and fix.

## Worked example

An invented page: a `.card` with a title that should grow at 400px, a sidebar with a nav that should become a row at 300px, a `.panel`, a `.tag` and a `.widget`. The agent runs the checker, which reports 6 warnings, 0 errors.

```
page.css:9  warn  bad-display  .tag: container-type on display: inline does not make a container ...
page.css:1  warn  name-no-type  .card: container name without container-type inline-size or size ...
page.css:6  warn  query-unknown-name  @container sidebar names a container that no scanned file declares ...
page.css:12  warn  self-style  .widget is a container and cannot be styled by its own query ...
page.css:5  warn  shrink-to-fit  .sidebar: inline-size container in a shrink-to-fit box collapses to 0 wide ...
page.css:8  warn  size-no-height  .panel: container-type size with no height, block-size or aspect-ratio collapses to 0 high ...
```

At 800px wide the browser agrees with the checker: the card title is 16px where 24px was meant, the nav is `block` where `flex` was meant, the sidebar is 0px wide, the panel is 0px high and the widget padding is 16px where 32px was meant.

Fixes in one batch (`page.css` only): a `.card-wrap` wrapper takes `container: card / inline-size`, the sidebar container is renamed to match its query and given `width: 100%`, the panel gets `height: 12rem`, the tag becomes `inline-block` with a width, and the widget gets a wrapper. The checker then reports 0 warnings. At 800px the browser gives a 24px title, a `flex` nav, a sidebar 800px wide, a panel 192px high and 32px widget padding. The title measured 16px at 300px and 399px and 24px at 400px and 600px, measured in Chromium 153 on 25 October 2026.

## Templates

### Ancestors that are containers (step 2)

```js
(sel) => { let el = document.querySelector(sel), out = [];
  for (let n = el.parentElement; n; n = n.parentElement) { const s = getComputedStyle(n);
    if (s.containerType !== 'normal' || s.containerName !== 'none') { const r = n.getBoundingClientRect();
      out.push(n.tagName.toLowerCase() + (n.className ? '.' + String(n.className).trim().split(/\s+/).join('.') : '') + ' | name ' + s.containerName + ' | type ' + s.containerType + ' | ' + Math.round(r.width) + 'x' + Math.round(r.height)); } }
  return out; }
```

Call it with a selector for the element, for example `('.card .title')`. It prints tag names, class names and sizes only.

### Measure a property at container widths (step 7)

```js
(wrap, sel, prop, widths) => widths.map(w => { const c = document.querySelector(wrap), o = c.style.width;
  c.style.width = w + 'px'; const v = getComputedStyle(document.querySelector(sel)).getPropertyValue(prop); c.style.width = o; return w + 'px: ' + v; })
```

Call it as `('.card-wrap', '.card .title', 'font-size', [300, 399, 400, 600])`. It restores the width after each read.

### Report

```
Container query report: <page>, <date>
Component: <name>. Container: <selector, name, type>. Breakpoint: <width, source>.
Checker before: warnings N. After: warnings N.
Changes: <file:line  issue  fix>   (one line each)
Measured: <property at widths, before and after>
Left as is: <warning  reason>
Not tested: <items>
```

### Starting prompt

```
Use the container-queries skill. Stylesheets and markup: <list>. Component: <name>, in slots of <widths>.
It should <change> when its container is narrower than <width, or "ask me">. Edit on a branch in batches of one component or 20 rules and show each diff first.
```

## Failure modes and anti-patterns

| Mistake | How to spot it | Do instead |
|---|---|---|
| Styling the container with its own query | `self-style`, and the rule never applies | Query a child, or add a wrapper |
| Naming a container and writing no type | `name-no-type` | `container: name / inline-size` |
| `container-type: size` without a height | `size-no-height`, and the component is 0px high | `inline-size`, or add a height |
| Inline-size container in a float or an auto-width flex item | `shrink-to-fit`, or the browser shows 0px width | `width: 100%`, `flex: 1` or a fixed width |
| Container on an inline, contents or table box | `bad-display` | A block, flex, grid or inline-block box |
| Copying the viewport breakpoint into the container query | A component that breaks at an arbitrary width | Measure where the content stops fitting |

## Quality bar

Run every item before handing over.

1. Checker warnings are 0, or each is listed with a reason.
2. Every query is named, and the container it names declares a type.
3. No rule restyles the container with its own query.
4. The measuring snippet shows the value changing at the breakpoint and not before.
5. The component was checked in each real slot.
6. No file under `node_modules` or a vendor folder was edited.
7. Every changed line appears in the report with file, line, issue and fix.
8. The report lists what was not tested.

## Cost

Figures are estimates from this skill's own count (words × 1.35 for prose, characters ÷ 3.5 for code and tables), not from a model tokeniser. Rival figures are estimates from the excerpts I read.

| Item | Tokens | Status | Read condition |
|---|---|---|---|
| A. Description (always loaded) | 113 | Estimate | Every session with the skill installed |
| B. `SKILL.md` body | 4,463 | Estimate | When the skill fires |
| C. `references/appendix.md` | 843 | Estimate | Only to check a claim or a measurement |
| C. `references/patterns.md` | 525 | Estimate | Only when writing or rebuilding a component's container and queries |
| Script output: `cq_check.py` (worked example) plus measuring snippets | 605 | Estimate | Every run. Checker output is capped with `head -40`, about 1,524 tokens |
| D. Typical run (A + B + script and snippet output) | 5,181 | Estimate | Common path: fix flagged lines, no references |
| D. Worst case (A + B + both references + 40 lines of output) | 8,068 | Estimate | Rebuilding a layout, many findings |
| E. Cheaper path | 4,956 | Estimate | Run the checker, fix only flagged lines, measure two container widths, skip the references |

Rival sizes, estimated from the excerpts I read (SKILL.md only, files they link were not sized): openskillindex container-query-architect about 350 to 400 words, roughly 475 to 540 tokens, no scripts; thedaviddias container-queries (tessl) about 230 words, roughly 310 tokens, tessl score 58; wshobson responsive-design (smithery) about 250 to 300 words, roughly 340 to 405 tokens, plus `references/details.md`. This skill costs more to load than all three. What it adds is a checker for the failures that silently stop a query matching, and a measuring step. On a stylesheet with no containers the checker printed 93 characters for a 235,601-character file, so the saving is the agent not reading the file. On the 12-line worked example its output is larger than the file, so on a stylesheet under about 100 lines read the file and use the ancestors snippet.

## Handoffs

`flexbox-grid-layout.md` covers overflow and shrinking inside the container. `cascade-layers.md` covers a container rule that loses to another rule.

## Appendix

Tool facts, measurements and sources are in `references/appendix.md`. Component recipes are in `references/patterns.md`.

## Sources

Last verified 25 October 2026 for every source, except where a date says otherwise.

- Anthropic, Skill authoring best practices: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices
- MDN, Container queries: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries
- web.dev, Baseline 2023 (updated 12 December 2023): https://web.dev/blog/baseline2023
- csswg-drafts issue 7667: https://github.com/w3c/csswg-drafts/issues/7667
- openskillindex, container-query-architect. Rival implementation: https://openskillindex.com
- thedaviddias, container-queries (via tessl). Rival implementation: https://tessl.io/registry/thedaviddias
- wshobson, responsive-design (via smithery.ai). Rival implementation: https://smithery.ai/skills/wshobson/responsive-design

---
Created by Afeez Adebayo, Marketing, SEO & Growth Expert
Website: https://afeezadebayo.com
LinkedIn: https://www.linkedin.com/in/afeez-adebayo/
X: https://x.com/za_icon
Source: https://afeezadebayo.com/skills-md/frontend-development/css-layout/container-queries/

Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). You may copy, share and adapt this skill, including for commercial use, as long as you credit Afeez Adebayo and link to the source URL above. Keep this notice in any copy or adapted version.
