← Back to DevBytes

Mastering CSS Container Queries: Tips and Best Practices

What Are CSS Container Queries?

Container queries allow you to style elements based on the size of their parent container, rather than the size of the entire viewport. This is a fundamental shift from media queries, which have been the go-to tool for responsive design for over a decade. With container queries, a component can adapt its layout, typography, or visibility based on how much space its immediate parent provides—regardless of where that component lives on the page.

The Problem Container Queries Solve

Imagine you've built a beautifully responsive card component. It looks perfect in the main content area on desktop, but when that same card gets placed in a narrow sidebar, it breaks. The card doesn't "know" it's in a sidebar—it only responds to viewport width via media queries. You end up writing hacks: extra CSS classes, modifier utilities, or duplicating styles to account for different contexts. Container queries eliminate this friction entirely. The component becomes truly self-aware of its own available space.

Container Queries vs Media Queries

Media queries ask: "How big is the browser window?"
Container queries ask: "How big is my parent element?"

This distinction is crucial. A component using container queries can be dropped into any layout—a wide hero section, a narrow sidebar, a multi-column grid, or even a modal—and it will automatically adjust to fit its surroundings. You write the component styles once, and they work everywhere.

Getting Started: Core Concepts

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Setting Up Containment

For an element to serve as a container for container queries, it must establish containment. This is done using the container-type property. You can query based on inline size (width in horizontal writing modes), block size (height), or both. The most common use case is inline size containment.

/* The parent element must declare containment */
.card-wrapper {
  container-type: inline-size;
}

Once an element has container-type set, it becomes a container query container. Its child elements can now respond to its dimensions using the @container at-rule.

The @container Syntax

The @container rule works similarly to @media, but instead of querying the viewport, it queries the nearest ancestor container that has the appropriate containment type.

/* Target the nearest ancestor with inline-size containment */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

@container (max-width: 399px) {
  .card {
    display: block;
  }
}

The query condition uses familiar range syntax: min-width, max-width, min-height, max-height, and logical property equivalents like min-inline-size.

Naming Containers

In complex layouts, an element may have multiple ancestor containers. You can explicitly name containers using container-name and target specific ones in your queries. This prevents ambiguity and ensures your component responds to the intended parent.

/* Name the container */
.sidebar {
  container-name: sidebar-container;
  container-type: inline-size;
}

.main-content {
  container-name: main-container;
  container-type: inline-size;
}

/* Query a specific named container */
@container sidebar-container (max-width: 300px) {
  .widget {
    flex-direction: column;
  }
}

/* Query the nearest unnamed or any inline-size container */
@container (min-width: 600px) {
  .article {
    font-size: 1.2rem;
  }
}

The container Shorthand

You can combine container-name and container-type using the container shorthand property:

/* Syntax: container: [name] / [type] */
.card-grid {
  container: card-container / inline-size;
}

/* Name only, type defaults to both dimensions */
.widget-area {
  container: widget-container;
}

/* Type only, no name */
.banner {
  container: inline-size;
}

Practical Examples

Example 1: A Self-Adapting Card Component

Let's build a card that switches from a stacked layout to a side-by-side layout when its container is wide enough. This card can be used anywhere without modification.

<!-- HTML -->
<div class="card-wrapper">
  <article class="card">
    <img class="card__image" src="photo.jpg" alt="A scenic mountain view">
    <div class="card__body">
      <h2 class="card__title">Mountain Adventure</h2>
      <p class="card__text">Experience the thrill of alpine hiking...</p>
      <button class="card__button">Learn More</button>
    </div>
  </article>
</div>
/* CSS */
.card-wrapper {
  container-type: inline-size;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.5rem;
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  background: #fff;
}

/* When the container is wider than 450px, switch to horizontal layout */
@container (min-width: 450px) {
  .card {
    flex-direction: row;
    align-items: start;
  }
  
  .card__image {
    width: 40%;
    max-width: 200px;
    object-fit: cover;
    border-radius: 8px;
  }
  
  .card__body {
    flex: 1;
  }
}

/* When the container is wider than 600px, increase content density */
@container (min-width: 600px) {
  .card {
    padding: 2rem;
    gap: 1.5rem;
  }
  
  .card__title {
    font-size: 1.5rem;
  }
  
  .card__text {
    font-size: 1.1rem;
    line-height: 1.6;
  }
}

Drop this card into a full-width section, a three-column grid, or a narrow sidebar—it adapts automatically to each context.

Example 2: Layout Switching Based on Container Width

Here's a product listing that shifts from a single-column list to a dense multi-column grid as the container grows.

<!-- HTML -->
<div class="products-container">
  <ul class="product-list">
    <li class="product-item">...</li>
    <li class="product-item">...</li>
    <li class="product-item">...</li>
    <li class="product-item">...</li>
  </ul>
</div>
/* CSS */
.products-container {
  container: products / inline-size;
}

.product-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Breakpoint 1: two columns */
@container products (min-width: 500px) {
  .product-list {
    grid-template-columns: repeat(2, 1fr);
    gap: 1.25rem;
  }
}

/* Breakpoint 2: three columns */
@container products (min-width: 750px) {
  .product-list {
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
  }
}

/* Breakpoint 3: four columns with smaller gaps */
@container products (min-width: 1000px) {
  .product-list {
    grid-template-columns: repeat(4, 1fr);
    gap: 1rem;
  }
}

Notice how the component scales its grid columns based purely on the container's width, not the viewport. Place it in a wide hero banner or a narrow drawer—it handles both gracefully.

Example 3: Combining Container Queries with Media Queries

Container queries and media queries are complementary. Use media queries for page-level layout (header, navigation, overall grid) and container queries for component-level adaptations.

/* Page layout via media queries */
@media (min-width: 768px) {
  .page-layout {
    display: grid;
    grid-template-columns: 250px 1fr;
  }
}

/* Component adaptation via container queries */
.profile-bio {
  container-type: inline-size;
}

@container (min-width: 350px) {
  .bio-content {
    display: flex;
    align-items: center;
    gap: 1.5rem;
  }
  
  .bio-avatar {
    width: 80px;
    height: 80px;
  }
}

@container (max-width: 349px) {
  .bio-content {
    text-align: center;
  }
  
  .bio-avatar {
    margin: 0 auto 1rem;
  }
}

The page layout responds to viewport changes, while the bio component responds to its container's size—which could change because the sidebar collapsed or the grid reconfigured.

Best Practices for Container Queries

1. Use the container Shorthand for Clarity

Always prefer the container shorthand property. It keeps your CSS concise and makes the relationship between name and type explicit in a single declaration.

/* Recommended */
.sidebar {
  container: sidebar / inline-size;
}

/* Less concise */
.sidebar {
  container-name: sidebar;
  container-type: inline-size;
}

2. Adopt Component-First Responsive Thinking

Container queries encourage a component-first mindset. Design each component with its own set of breakpoints based on realistic container widths, not arbitrary viewport sizes. Ask: "At what width does this component look cramped? At what width does it have room to expand?" Define breakpoints around those thresholds.

/* Think: "This component works well at these container widths" */
@container (min-width: 300px) { /* Comfortable minimum */ }
@container (min-width: 500px) { /* Spacious layout */ }
@container (min-width: 700px) { /* High-density variant */ }

3. Avoid Deep Nesting of Container Relationships

While you can have nested containers (a container inside another container), avoid creating deeply nested chains of container queries that depend on each other. This makes debugging difficult and can lead to unpredictable cascading behavior. Keep containment hierarchies shallow and intentional.

/* Good: clear, shallow container relationship */
.page-section {
  container: section / inline-size;
}

/* Avoid: deeply nested, interdependent containers */
/* .outer > .middle > .inner all with container-type set
   and queries that chain through all three */

4. Use Meaningful, Descriptive Container Names

Name your containers after their role or layout region, not after their visual appearance. Good names include sidebar, main-content, product-grid, modal-body. Avoid names like wide-container or red-box—these become misleading when the design evolves.

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

.comment-thread {
  container: comment-thread / inline-size;
}

/* Avoid vague or visual names */
/* container: box1 / inline-size;  ← Bad */
/* container: wide-area / inline-size; ← Becomes wrong if it narrows */

5. Test Components in Multiple Contexts

The whole point of container queries is reusability across different layouts. Actively test your components inside narrow slots, wide expanses, sidebars, modals, and grid cells. Use a "component playground" page where you can visually verify behavior at various container widths.

/* Create a test harness for your components */
.test-harness {
  container-type: inline-size;
  resize: horizontal;
  overflow: auto;
  border: 2px dashed #ccc;
  padding: 1rem;
  min-width: 200px;
  max-width: 900px;
}

/* Now drag the resize handle to test container query breakpoints */

Adding resize: horizontal and overflow: auto to a container gives you an interactive resizing handle in the browser—perfect for manual testing and debugging.

6. Provide Sensible Fallbacks for Older Browsers

Container queries are supported in all modern browsers (Chrome 105+, Firefox 110+, Safari 16+, Edge 105+). For older browsers, provide a reasonable fallback by writing default styles that work without container query overrides. The @container rule is simply ignored in unsupported browsers, so your base styles become the fallback automatically.

/* Base styles act as fallback for older browsers */
.card {
  display: flex;
  flex-direction: column; /* Safe default: stacked layout */
  gap: 1rem;
}

/* Enhanced layout for supporting browsers */
@container (min-width: 450px) {
  .card {
    flex-direction: row; /* Upgraded experience */
  }
}

This progressive enhancement approach means your components remain functional everywhere, while delivering an optimized experience where container queries are supported.

7. Combine Container Queries with Custom Properties for Theming

Container queries become even more powerful when paired with CSS custom properties. You can adjust spacing scales, font sizes, or color intensities based on container size.

.theme-container {
  container: theme / inline-size;
}

/* Dense, compact spacing for narrow containers */
@container theme (max-width: 400px) {
  .content {
    --spacing-unit: 0.5rem;
    --font-scale: 0.9;
    padding: var(--spacing-unit);
    font-size: calc(1rem * var(--font-scale));
  }
}

/* Generous spacing for wide containers */
@container theme (min-width: 700px) {
  .content {
    --spacing-unit: 2rem;
    --font-scale: 1.15;
    padding: var(--spacing-unit);
    font-size: calc(1rem * var(--font-scale));
  }
}

8. Use Logical Properties for Internationalization

When writing container queries, prefer logical properties like inline-size and block-size over physical properties like width and height. This ensures your queries work correctly across different writing modes (e.g., vertical Japanese text or right-to-left Arabic).

/* Physical: tied to horizontal-tb writing mode */
@container (min-width: 500px) { ... }

/* Logical: works in any writing mode */
@container (min-inline-size: 500px) { ... }

/* Container type also benefits from logical thinking */
.container {
  container-type: inline-size; /* Queries inline axis */
}

Conclusion

CSS Container queries represent a paradigm shift in responsive design—from viewport-centric layouts to truly component-centric architecture. They allow you to build self-aware components that gracefully adapt to whatever space they occupy, eliminating the need for context-specific overrides and brittle modifier classes. By setting up containment, writing @container rules with meaningful breakpoints, and following best practices like shallow containment hierarchies, descriptive naming, and progressive enhancement, you can create robust, reusable components that work beautifully across every layout context. Pair container queries with media queries for page-level structure, leverage custom properties for dynamic theming, and test thoroughly with interactive resize handles. The result is a cleaner codebase, faster development cycles, and a user experience that feels tailor-made—no matter where a component lives on the page.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles