skip to content

Why can a reusable card component not be made truly responsive with CSS @media queries alone, and what do container queries change?

level: middleimportance: must knowfreq 68%

answer

  1. viewport is the wrong measuring stick
  2. same component, two different column widths
  3. breakpoint belongs to the component
  4. an ancestor opts in with container-type
  5. @container replaces @media inside the component

basics

~20 s

@media queries only test the viewport, so one card gets the same breakpoint in a wide main column and in a narrow sidebar. Container queries let a component respond to the size of its own container instead.

solid answer

~50 s

A media query asks about the viewport, but a reusable component's layout depends on the space it was dropped into, not on the window. The same card in a 900px main column and in a 280px sidebar sees identical `@media` conditions, so the fix has to come from outside the component — a `card--compact` modifier the page must remember to apply, or a `.sidebar .card` override that couples the component to one placement. Container queries invert that: an ancestor opts in with `container-type: inline-size`, and the card's own stylesheet writes `@container (min-width: 400px) { … }`. The breakpoint now belongs to the component and travels with it, so the same markup goes two-column in the main area and stacks in the sidebar with no page-level help. Media queries still own genuinely viewport-level things: the page shell, print, and user-preference queries. Support has been broad in Chrome, Safari and Firefox since 2023.

go deeper

for a junior

Be able to say plainly that a media query measures the browser window while a container query measures an ancestor element, and that the ancestor must declare container-type first.

for a middle

Explain how @container resolves against the nearest ancestor query container, and show the wrapper-plus-child pattern that makes one card behave differently in a sidebar and a main column.

for a senior

Show judgment about where containers are declared in a real component library, what still belongs to media queries, and how you keep the layout acceptable where the at-rule is ignored.

for a principal

Own the boundary: which layout decisions belong to the page shell versus the component, and how a team encodes that split so components stay portable across products rather than accumulating placement-specific overrides.

## The mismatch between viewport and component A CSS media query answers questions about the **viewport** — the window's width and height, its orientation, resolution, and the user's stated preferences. That model fit an era when a page was a single design that reflowed as a whole: the layout changed at 768px and every part of it changed at the same moment. Component-based UIs broke the assumption. A "product card" is authored once and then dropped into a wide main column on one page, a 280px sidebar on another, a three-across grid on a third, and a modal on a fourth. Whether the card should put its image beside the text or above it depends on how much inline space it was handed — and the viewport says nothing about that. On a 1400px desktop the sidebar card has 280px to work with, but a `@media (min-width: 900px)` rule fires anyway and gives it a two-column layout that immediately overflows. The classic workarounds all leak layout knowledge out of the component: - modifier classes such as `card--compact` that the **page** has to remember to apply; - placement-coupled overrides like `.sidebar .card { … }`, which break the first time the card moves; - a script that measures the box after layout and re-adds a class. Each one is a maintenance tax, and each one is wrong again as soon as somebody uses the component somewhere new. ## What a container query does instead A container query evaluates against an **ancestor element** rather than the viewport. Two pieces are required. First, some ancestor must opt in to being a query container: ```css .card-wrapper { container-type: inline-size; } ``` Second, the component's own rules are wrapped in `@container`: ```css .card { display: grid; gap: 1rem; } @container (min-width: 400px) { .card { grid-template-columns: 8rem 1fr; } } ``` An unnamed `@container` rule resolves against the **nearest ancestor query container** of each matched element. So the very same `.card` rule produces a stacked layout inside a 280px sidebar wrapper and a two-column layout inside a 900px main-column wrapper, with no page-level cooperation at all. Conditions use the same comparison vocabulary as media queries, including range syntax: `@container (width >= 400px)` is equivalent to the `min-width` form above. CSS also provides container-relative length units so a component can size type and spacing against its container rather than the window; they are a separate topic, but they exist for exactly this reason. ## The opt-in is deliberate `container-type` defaults to `normal`, which means no element is a size query container until you say so. That is not an oversight. Making an element a size query container applies **containment** to it — the browser gets a promise that the element's contents cannot influence its size in the queried axis, which is what makes the query resolvable without circularity. You would not want that applied to every element on the page, and you would not want the layout side effects either. ## Why the card cannot be its own container If `.card` both declares `container-type` and is the target of `@container`, nothing happens. The rule matches against the element's **ancestor** container, never itself — otherwise a rule could change the very width it was querying and loop forever. In practice this is the single most common beginner mistake: put `container-type` on a wrapper element and style the children. ```css /* wrong: the card can never match its own query */ .card { container-type: inline-size; } @container (min-width: 400px) { .card { … } } /* right: wrapper is the container, card is the queried subject */ .card-wrapper { container-type: inline-size; } @container (min-width: 400px) { .card { … } } ``` ## What media queries still own Container queries do not retire media queries. Anything that is genuinely a property of the output device or the user remains a media query: the page shell and overall page composition, `print`, resolution, `orientation`, and user preferences such as colour-scheme or reduced-motion. A good rule of thumb is that media queries decide the **page**, container queries decide the **component**. ## Practical notes Container queries shipped in Chrome 105, Safari 16 and Firefox 110, so as of 2026 they are broadly available and no longer need a fallback in most products. When you do need one, the graceful path is to write the narrow, single-column layout as the base styles and let the `@container` block be pure enhancement — a browser that ignores the at-rule then renders the compact form, which is usually acceptable rather than broken.

  • If container queries handle components, is there anything left that genuinely needs a media query?
    Yes — anything that is a property of the device or the user rather than of available space. Page-level composition, `print` styles, `orientation`, resolution, and user-preference queries such as colour scheme or reduced motion have no container equivalent. The useful split is: media queries decide the page shell, container queries decide the components inside it.
  • How would you ship a container-query layout in a codebase that still has to render acceptably where the at-rule is unsupported?
    Write the narrow, single-column layout as the unconditional base styles and put only the wider layout inside `@container`. A browser that does not understand the at-rule drops that block and renders the compact version, which is degraded but usable. Feature-detect explicitly with `@supports (container-type: inline-size)` if you need to branch further.
  • Does adopting container queries mean every component should declare its own container?
    No. Each `container-type` declaration applies containment and layout side effects to that element, and deeply nested containers make resolution harder for a reader to follow. Declare containers at the few structural slots where components are actually placed — a grid cell, a sidebar, a card wrapper — and let components query whichever one is nearest.

saying these in an interview costs you the question

  • Says container queries make media queries obsolete
  • Claims @media (min-width) measures the parent element
  • Thinks @container works without any container-type declared
  • Puts container-type on the element it then tries to style
  • Solves it with page-specific modifier classes and calls that responsive

context