CSS Container Queries

What You Learn From This Page

  • Why component space can be a better responsive input than viewport width.
  • How container-type, container-name, and the container shorthand work.
  • How unnamed, named, nested, range, size, and style queries select a container.
  • How cqi, cqw, cqb, cqmin, and related units behave.
  • How to combine container queries with Grid, Flexbox, fluid values, logical properties, and media queries.
  • How to design accessible breakpoints, progressive fallbacks, tests, and debugging workflows.

Introduction

Responsive design traditionally asks how large the browser viewport is. That question is useful for page-level composition, but it is often the wrong input for a reusable component. A profile card can appear in a wide article, a narrow sidebar, a dashboard tile, a modal, or a user-resizable panel. The viewport may be large while the card itself receives very little space.

CSS container queries let descendant styles respond to the size or exposed style state of an ancestor container. A card can switch from stacked to horizontal only when its own region is wide enough. The same component class can therefore adapt across layouts without page-specific modifiers or knowledge of the application shell.

Container queries do not replace media queries, intrinsic layout, or fluid functions. They add a component-level decision tool. Grid and Flexbox can handle natural wrapping, CSS math can scale values continuously, container queries can change component structure, and media queries can respond to viewport or environmental conditions.

Why Container Queries Exist

Consider a page that is 1,440 pixels wide with a 280-pixel sidebar. A viewport media query sees a large screen and might turn every card into a two-column layout. A card inside the sidebar does not have enough room for that structure, even though the viewport condition matches.

A container query evaluates the card's relevant ancestor instead. The card in main content can become horizontal while the same card in the sidebar remains stacked. Responsive behavior follows the space actually assigned by layout.

Media Queries and Container Queries

A media query evaluates viewport and environment features. It is appropriate for page columns, navigation modes, printing, reduced motion, color-scheme preferences, input capabilities, and other global concerns. A container size query evaluates an eligible ancestor and is appropriate for local component decisions.

/* Page-level decision */
@media (min-width: 60rem) {
  .page { grid-template-columns: 16rem 1fr; }
}

/* Component-level decision */
@container (min-width: 30rem) {
  .card { grid-template-columns: 10rem 1fr; }
}

Neither query is inherently more modern or more correct. The deciding question is whether the requirement belongs to the environment or to the component's allocated space.

Basic Container Query Setup

A size query needs an ancestor that establishes a suitable query container. The most common setup gives a wrapper container-type: inline-size, then places responsive rules on a descendant.

.card-wrapper {
  container-type: inline-size;
}

.card {
  display: grid;
  gap: 1rem;
}

@container (min-width: 30rem) {
  .card {
    grid-template-columns: 10rem minmax(0, 1fr);
  }
}

The base card remains usable when the wrapper is narrow. At 30rem or wider, the query adds the two-column structure.

Understanding container-type

The container-type property determines which size features an element can expose for container queries. The central values are normal, inline-size, and size. Establishing a size query container also changes how the element participates in layout containment, so the property should be applied deliberately.

Most component queries need only the available line dimension and should use inline-size. Two-axis size queries have stronger sizing consequences and are appropriate only when both dimensions are intentionally controlled.

container-type: inline-size

inline-size enables size queries on the container's inline axis. In ordinary horizontal writing, this generally corresponds to width. In vertical writing, the physical orientation differs because the inline axis follows text flow.

.component-region {
  container-type: inline-size;
}

This is the practical default for cards, toolbars, form groups, dashboard widgets, and similar components that rearrange according to available line space.

container-type: size

The size value enables queries across both size axes and applies stronger size containment. A container cannot rely on its descendants to determine the same contained dimensions in the ordinary way, so an unconstrained block size can produce a collapsed or unexpected result.

.viewport-widget {
  container-type: size;
  inline-size: 24rem;
  block-size: 18rem;
}

Use it when the layout supplies definite dimensions and the component truly needs both width-like and height-like conditions. Do not choose it merely because it sounds more complete.

container-type: normal

normal is the initial value and does not make the element eligible for ordinary size queries. Style-query behavior is a separate concern, but a standard width or inline-size query requires a qualifying ancestor with an appropriate container type.

If an @container size rule never matches, missing container-type is one of the first things to inspect.

Named Containers

An unnamed query selects the nearest ancestor that can evaluate its features. In nested interfaces, that may not be the ancestor the author intended. container-name gives a query an explicit target context.

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

@container dashboard (min-width: 40rem) {
  .summary-widget {
    grid-template-columns: 1fr 1fr;
  }
}

The browser searches ancestors for an eligible container with the requested name. Naming is especially useful when several wrappers establish query containers.

The container Shorthand

The container shorthand combines a name and type, separated by a slash.

.dashboard-panel {
  container: dashboard / inline-size;
}

This is equivalent to assigning container-name: dashboard and container-type: inline-size. Use the shorthand when both values are set together and the concise form remains obvious to the team.

Nearest Eligible Ancestor

For an unnamed size query, the browser selects the nearest ancestor that supports the queried feature. A page shell, card region, and nested control group may all be query containers; a descendant query can therefore bind to a nearer wrapper than expected.

Eligibility matters as well as distance. A named container must have the requested name and support the feature used by the query. Use browser DevTools and explicit names when nested selection becomes difficult to reason about.

Why a Container Does Not Query Its Own Size

The element being styled by a container size query uses an ancestor query container. Styling a box based on its own resulting size could create a circular dependency: the style changes size, which changes the condition, which changes the style again.

<div class="card-region">
  <article class="card">...</article>
</div>

.card-region { container-type: inline-size; }

@container (min-width: 30rem) {
  .card { grid-template-columns: 10rem 1fr; }
}

A wrapper-and-child structure makes the dependency explicit and avoids the mistaken expectation that the card can use itself as its size-query container.

Traditional Query Syntax

Container size conditions support familiar minimum and maximum feature syntax.

@container (min-width: 30rem) {
  .card { /* comfortable state */ }
}

@container (max-width: 19.99rem) {
  .card { /* constrained state */ }
}

A compact mobile-first base often removes the need for a maximum query. Add enhancements as the container gains room, so unsupported or unmatched queries leave a functional component.

Range Syntax

Comparison syntax can express thresholds and intervals directly. The following rule targets containers whose width is at least 20rem and below 40rem.

@container (20rem <= width < 40rem) {
  .card {
    padding: 1.5rem;
  }
}

Use one syntax consistently within a project and verify browser requirements. Range expressions can be easier to read when a component has named compact, comfortable, and expanded states.

Combining Query Conditions

Container conditions can combine compatible features with logical operators. This is useful when a state requires more than one constraint, although complex expressions should remain explainable.

@container (width >= 30rem) and (width < 50rem) {
  .card { /* medium state */ }
}

@container not (width < 30rem) {
  .card { /* at least 30rem */ }
}

When conditions become difficult to scan, split states into clear thresholds or reconsider whether intrinsic layout can solve part of the requirement.

Compact, Comfortable, and Expanded States

Container breakpoints should describe component capability rather than device categories. A card can begin in a compact stacked state, gain a media column when comfortable, and increase its spacing only when expanded.

.card {
  display: grid;
  gap: 1rem;
}

@container (min-width: 30rem) {
  .card {
    grid-template-columns: 8rem minmax(0, 1fr);
  }
}

@container (min-width: 50rem) {
  .card {
    grid-template-columns: 12rem minmax(0, 1fr);
    gap: 2rem;
  }
}

The labels are conceptual; the CSS uses content-tested dimensions rather than assumptions about phones, tablets, or desktops.

Choose Breakpoints from Content

Place the component in a resizable region and reduce its inline size until the current arrangement becomes crowded, wraps poorly, or loses hierarchy. That failure point suggests a threshold. Then test realistic labels, translated content, validation messages, and user-generated values.

A breakpoint is not a target device width. It is the amount of container space required for a particular component state to remain usable.

Container Query Units

Container query units express lengths relative to a query container. cqw and cqh use physical width and height. cqi and cqb use logical inline and block dimensions. cqmin uses the smaller relevant dimension, while cqmax uses the larger.

One unit equals one percent of the corresponding query-container dimension. Logical units are often preferable for components intended to support different writing modes.

Fluid Values with cqi and clamp()

Unbounded container units can become too small in a narrow tile and too large in a wide panel. clamp() keeps a fluid value within useful limits.

.card-title {
  font-size: clamp(1.25rem, 5cqi, 2rem);
}

.card {
  padding: clamp(1rem, 4cqi, 2rem);
  gap: clamp(0.75rem, 3cqi, 1.5rem);
}

The title and spacing respond to the component context rather than the viewport. Root-relative bounds help preserve readable extremes and user font preferences.

Container Units Versus Viewport Units

A value in vw grows with the browser window even when the component remains trapped in a narrow sidebar. A value in cqi follows the inline size of the relevant container, so typography and spacing stay proportional to the space the component actually receives.

Viewport units remain appropriate when the visual relationship is genuinely tied to the viewport. Choose the reference system that owns the design decision rather than replacing every vw mechanically.

When No Eligible Container Exists for a Unit

Container query units need a relevant query-container dimension. When no eligible container is available, they fall back to the corresponding small viewport unit rather than becoming zero. That fallback can hide a missing container because the style still produces a visible result.

If a cqi-based value appears tied to the viewport, inspect ancestor container types and names. Do not rely on fallback behavior as the normal component architecture.

Container Queries with CSS Grid

Grid is a natural partner for structural component changes. A card can remain one column until its region can support media and content tracks.

.product-region {
  container-type: inline-size;
}

.product {
  display: grid;
  gap: 1rem;
}

@container (min-width: 36rem) {
  .product {
    grid-template-columns: minmax(10rem, 40%) minmax(0, 1fr);
    align-items: start;
  }
}

The minmax(0, 1fr) track helps long content shrink instead of forcing overflow.

Container Queries with Flexbox

A toolbar or action group can switch its main-axis arrangement when the containing region has enough space.

.toolbar-region {
  container-type: inline-size;
}

.toolbar {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@container (min-width: 30rem) {
  .toolbar {
    flex-direction: row;
    align-items: center;
  }
}

Before adding a query, test whether flex-wrap, flexible bases, and gap already produce an acceptable intrinsic result.

Intrinsic Layout Before Explicit Queries

Many responsive behaviors do not require conditions. Grid auto-fit with minmax() can change column count automatically, and Flexbox can wrap controls when space is limited.

.card-list {
  display: grid;
  grid-template-columns:
    repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem;
}

Use intrinsic layout for continuous space distribution. Add a container query when the component needs an explicit structural or presentational state that intrinsic behavior cannot express cleanly.

Container Queries Versus Fluid Sizing

A query creates a conditional change; a fluid function creates gradual change. If a heading only needs to scale smoothly, clamp(1.25rem, 5cqi, 2rem) may be sufficient. If the media moves beside the text at a threshold, an @container rule is appropriate.

A robust component often uses both: fluid spacing throughout its range and one or two content-driven structural thresholds.

Dashboard Widgets

Dashboards demonstrate the value of component-driven responsiveness. Users may resize panels, administrators may configure columns, and the same widget may occupy a quarter, half, or full row.

.widget-panel {
  container: widget / inline-size;
}

.widget {
  display: grid;
  gap: 1rem;
}

@container widget (min-width: 40rem) {
  .widget {
    grid-template-columns: 1fr 1fr;
  }
}

The widget needs no knowledge of dashboard track count. It responds to the panel that actually owns its available space.

Profile Card Pattern

A compact profile card can center its avatar and text, then become a horizontal identity row in a wider region.

.profile-region {
  container-type: inline-size;
}

.profile {
  display: grid;
  gap: 1rem;
  text-align: center;
}

@container (min-width: 28rem) {
  .profile {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    text-align: start;
  }
}

Logical text alignment makes the expanded state work naturally in both LTR and RTL contexts.

Form Component Pattern

Related form fields can stack by default and share a row only when their region is sufficiently wide.

.form-region {
  container-type: inline-size;
}

.form-row {
  display: grid;
  gap: 1rem;
}

@container (min-width: 32rem) {
  .form-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

Labels, hints, and errors must still fit. A two-column state should be chosen from the longest realistic field content rather than visual symmetry alone.

Search and Action Pattern

A search field and submit action often need a vertical compact state and a horizontal comfortable state.

.search-region {
  container-type: inline-size;
}

.search-actions {
  display: grid;
  gap: 0.75rem;
}

@container (min-width: 25rem) {
  .search-actions {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

The button remains available in both states. Responsiveness changes arrangement, not functionality.

Container Names in Nested Components

A card may contain a toolbar that is itself responsive. If both wrappers are unnamed query containers, card descendants can accidentally bind to the toolbar region or vice versa. Naming stable responsive contexts makes dependencies visible.

.card-region { container: card / inline-size; }
.toolbar-region { container: toolbar / inline-size; }

@container card (min-width: 36rem) {
  .card-body { grid-template-columns: 12rem 1fr; }
}

@container toolbar (min-width: 24rem) {
  .toolbar-actions { flex-direction: row; }
}

Name the context, not its current size. Names such as card and toolbar remain valid when thresholds change.

Style Queries

Style queries allow descendants to respond to a style state exposed by an ancestor, commonly through a custom property. They answer a different question from size queries: not how large the container is, but what value or variant it provides.

.card-region {
  --card-variant: featured;
}

@container style(--card-variant: featured) {
  .card {
    border-color: #175cd3;
    background: #eff8ff;
  }
}

Style-query capabilities and support can evolve, so verify the exact syntax and browser matrix required by the project. Keep a usable default when enhancement is unavailable.

Size and Style Queries Together

A component can respond independently to available space and semantic variant. The size query may change layout, while a style query changes appearance. Keeping those concerns separate avoids using width as a proxy for meaning.

Do not encode a featured state only because a card happens to be wide. Expose a variant through markup or a custom property, and use size solely for spatial decisions.

Media, Container, and Preference Queries

A strong architecture assigns each mechanism a clear responsibility. Media queries can set the page shell and respond to user preferences such as reduced motion or color scheme. Container queries can rearrange cards and controls. Fluid functions can scale values between useful limits.

@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
}

@container card (min-width: 34rem) {
  .card { grid-template-columns: 11rem 1fr; }
}

.card-title {
  font-size: clamp(1.25rem, 5cqi, 2rem);
}

Logical Properties and Writing Modes

Container queries become more reusable when component styles use logical properties. padding-inline, margin-block, inline-size, and text-align: start follow writing direction while the query manages structural space.

Prefer cqi when a fluid value should follow the logical inline dimension. Physical cqw remains correct when the design is explicitly tied to physical width.

Accessibility at Narrow Container Sizes

A compact component must preserve information and actions. Hiding an important button because a panel is narrow removes functionality for users who zoom, resize, or use split-screen windows. Better responses include stacking controls, allowing wrapping, using a disclosed menu with an accessible name, or reducing nonessential decoration.

Test keyboard access, visible focus, error messages, touch target sizes, screen-reader names, and source order in every component state. A visually efficient compact layout is not successful if interaction becomes incomplete or confusing.

DOM Order and Visual Rearrangement

Container queries can change Grid areas, flex direction, and the order property, but these visual changes do not rewrite document or default keyboard order. A component that looks reordered may be read or focused in a different sequence.

Keep a meaningful source order that works in every state. Use visual reordering only when it does not change comprehension or task sequence.

Zoom and Reflow Testing

Browser zoom reduces the effective CSS space available to components, so container queries can naturally switch them into compact states. This is beneficial when the compact state remains complete and readable.

Test at 100, 125, 150, 200, and higher percentages required by the project. Look for clipped labels, fixed block sizes, overlapping controls, code or URLs that force overflow, and queries that switch repeatedly near a threshold.

Progressive Enhancement

Start with a usable constrained layout outside every query. Supporting browsers receive enhanced structures when conditions match; older or unsupported environments retain the base component.

.card {
  display: grid;
  gap: 1rem;
}

@container (min-width: 30rem) {
  .card {
    grid-template-columns: 10rem minmax(0, 1fr);
  }
}

If the query is unavailable, the stacked card still works. An @supports (container-type: inline-size) wrapper is available when setup declarations or alternate fallbacks require explicit capability detection.

Common Mistake: Missing a Query Container

An @container rule alone does not establish its context. Without an eligible ancestor, a size condition cannot match. Add container-type: inline-size to the wrapper that owns the component's available space.

Do not place the property on every element globally. Excess containers complicate selection and can introduce containment behavior that changes sizing.

Common Mistake: Choosing size by Default

container-type: size is not a stronger version of inline-size that should be used everywhere. Its two-axis containment can prevent descendants from contributing to the container's dimensions as expected. Components with automatic block size may collapse or need explicit sizing.

Use inline-size for ordinary width-like component responsiveness and choose size only after confirming the two-axis requirement and layout consequences.

Common Mistake: Too Many Breakpoints

Thresholds every few rem create brittle components and difficult test matrices. First try intrinsic Grid or Flexbox, fluid values, wrapping, minmax(), and content-aware sizing. Use queries for meaningful state changes rather than one-pixel visual adjustments.

Most components need a compact base and perhaps one or two expanded states. More states should have clear design reasons.

Common Mistake: Viewport Dependencies Return

A component may use an @container layout but still size typography with unbounded vw. It then becomes locally structured but globally scaled, producing oversized content in narrow regions on wide screens.

Use container units with sensible bounds when the value belongs to component space. Retain viewport units only when the viewport is truly the intended reference.

Common Mistake: No Functional Base State

If all useful layout and visibility declarations exist only inside a matching query, the component may break when its container is smaller, missing, or unsupported. The base state should contain every essential label, action, and content relationship.

Queries enhance arrangement and density. They should not be the only place where the component becomes usable.

Performance Considerations

Container queries are designed for normal component use, and ordinary applications should prioritize clear architecture. Avoid making every node a query container or creating deeply nested networks of tiny thresholds. Such designs are difficult to reason about even before performance becomes measurable.

If interaction or resizing is slow, profile the production page. Large DOM trees, expensive layout, images, JavaScript updates, and paint effects may contribute more than the query mechanism itself.

Debugging Container Queries

When a rule does not match, verify that an ancestor has the required container-type, that a named query finds the correct container-name, and that the condition uses a supported dimension. Measure the actual container rather than the viewport.

Check whether a nearer unnamed container is selected, whether the rule styles a descendant rather than the container itself, whether another cascade declaration overrides the matched result, and whether containment altered the wrapper's size. For container units, confirm which ancestor supplies their reference.

Using Browser DevTools

Modern browser developer tools can identify query containers, display dimensions, show matched or inactive query rules, and trace computed styles. Resize the owning panel while watching each threshold become active.

DevTools is especially important for nested containers because visual proximity does not prove which ancestor was selected. Use meaningful names and inspect the computed container properties to make that relationship explicit.

Testing Reusable Components

A component test matrix should render each component in several container sizes and contexts rather than only several viewport widths. Include narrow sidebar, medium grid cell, wide main area, modal, translated content, zoom, and RTL where supported.

Automated visual tests can set wrapper widths directly. Interaction tests should verify that actions, focus order, validation, disclosure controls, and dynamic content continue to work in every structural state.

Container Query Decision Guide

RequirementPrimary ToolReason
Page columns change with viewportMedia queryThe page environment owns the decision
Card layout changes with assigned spaceContainer queryThe component region owns the decision
Grid adds tracks automaticallyIntrinsic GridNo explicit state is required
Controls wrap naturallyFlex wrappingAvailable space can be handled intrinsically
Typography scales smoothlyclamp() with cqiA continuous value is sufficient
User prefers reduced motionMedia featureThis is an environmental preference
Component reacts to exposed variantStyle queryThe condition represents style state

Container Query Reference

FeatureMeaningTypical Use
container-type: inline-sizeEnables inline-axis size queriesMost responsive components
container-type: sizeEnables two-axis size queriesExplicitly sized regions
container-nameNames a query contextNested container clarity
containerName and type shorthandConcise setup
@containerApplies conditional descendant stylesComponent state changes
cqw / cqhPhysical container dimensionsWidth- or height-relative lengths
cqi / cqbLogical container dimensionsWriting-mode-aware lengths
cqmin / cqmaxSmaller or larger container dimensionBalanced component scaling
style(...)Queries an exposed style stateVariants and semantic context

Common Interview Questions

What is a CSS container query? It conditionally applies styles according to an eligible ancestor container's size or exposed style state rather than relying only on the viewport.

Why are container queries useful? They make a component responsive to the space it actually receives, improving portability across sidebars, grids, dialogs, and main content.

How do you establish a size query container? Apply container-type: inline-size to the relevant wrapper for most width-like component queries.

What does container-name do? It lets an @container rule target a specific named ancestor instead of relying on the nearest unnamed eligible container.

What is cqi? One cqi equals one percent of the relevant query container's inline size.

Can the queried container style itself from its own size? A size query applies to descendants using an ancestor query container; the common pattern separates container and responsive child.

Do container queries replace media queries? No. Media queries remain appropriate for viewport and environmental decisions, while container queries handle component context.

Practical Review Checklist

  • Use container-type: inline-size for ordinary component size queries.
  • Choose size only when both axes and containment effects are intentional.
  • Provide a complete compact base state before query enhancements.
  • Select thresholds from content failure points rather than device categories.
  • Use names when nested query contexts could be ambiguous.
  • Try intrinsic Grid, Flexbox, and wrapping before adding a breakpoint.
  • Use bounded container units for continuous local scaling.
  • Keep media queries for page and environmental concerns.
  • Preserve actions, information, source order, focus visibility, and touch targets.
  • Combine queries with logical properties for RTL and writing-mode support.
  • Test zoom, long text, localization, dynamic updates, and real embedding contexts.
  • Inspect the actual selected container and matched conditions in DevTools.
  • Avoid excessive containers, thresholds, and nested query dependencies.
  • Verify style-query and advanced syntax support against project requirements.

Key Takeaway

Container queries make responsive design component-driven. A wrapper establishes a query context, a descendant provides a complete constrained layout, and @container enhances that component only when its allocated space or exposed style state supports the change. Named containers make nested dependencies explicit, while container units provide fluid values tied to local context.

Use the simplest responsive mechanism for each job: intrinsic Grid and Flexbox for natural adaptation, bounded container units for continuous scaling, container queries for structural component states, and media queries for viewport or environmental decisions. Choose breakpoints from real content, preserve functionality at every size, and test components where they will actually be embedded. The result is reusable UI that responds to its own space rather than guessing from the browser window.