Skip to main content
โšก Calmops

CSS Container Queries: Component-Based Responsive Design

CSS Container Queries represent a paradigm shift in responsive web design. Instead of querying the viewport size, container queries allow you to style elements based on their parent container’s size. This comprehensive guide covers everything you need to know about CSS Container Queries.

What are Container Queries?

Container queries enable component-based responsive design by allowing styles to respond to the size of their containing element rather than the viewport.

/* Traditional Media Query - based on viewport */
@media (max-width: 768px) {
  .card {
    flex-direction: column;
  }
}

/* Container Query - based on parent container */
@container (max-width: 400px) {
  .card {
    flex-direction: column;
  }
}

The Problem with Media Queries

<!-- Media queries don't work well for reusable components -->
<div class="sidebar">
  <!-- This card won't adapt to sidebar width -->
  <article class="card">
    <img src="thumbnail.jpg">
    <div class="content">
      <h3>Card Title</h3>
      <p>Card content...</p>
    </div>
  </article>
</div>

<div class="main-content">
  <!-- Same card, different context -->
  <article class="card">
    <img src="thumbnail.jpg">
    <div class="content">
      <h3>Card Title</h3>
      <p>Card content...</p>
    </div>
  </article>
</div>
/* This affects ALL cards regardless of container */
@media (max-width: 600px) {
  .card {
    flex-direction: column;
  }
}

The Container Query Solution

/* Define a container */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* Query the container instead */
@container card (max-width: 400px) {
  .card {
    flex-direction: column;
  }
}

Setting Up Container Queries

container-type Property

/* Three types of container queries */

/* Query inline size (width) - most common */
.container {
  container-type: inline-size;
}

/* Query block size (height) */
.container {
  container-type: block-size;
}

/* Query both dimensions */
.container {
  container-type: size;
}

/* Default: no containment */
.container {
  container-type: normal;
}

container-name Property

/* Named containers for more specific targeting */
.sidebar {
  container-name: sidebar;
  container-type: inline-size;
}

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

/* Query specific containers */
@container sidebar (max-width: 200px) {
  .card {
    font-size: 0.875rem;
  }
}

@container main (max-width: 600px) {
  .card {
    font-size: 1rem;
  }
}

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

Container Query Units

Container queries introduce new CSS units relative to the container:

.container {
  container-type: inline-size;
  width: 300px;
}

.child {
  /* cqw - 1% of container width */
  width: 50cqw;
  
  /* cqh - 1% of container height */
  height: 50cqh;
  
  /* cqi - inline size (width in horizontal) */
  font-size: 2cqi;
  
  /* cqb - block size */
  padding: 2cqb;
  
  /* cqmin - smaller of cqi or cqb */
  margin: 1cqmin;
  
  /* cqmax - larger of cqi or cqb */
  gap: 1cqmax;
}

Practical Unit Usage

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

.card-title {
  /* Scales with container */
  font-size: clamp(1rem, 5cqi, 2rem);
}

.card-body {
  /* Padding scales with container */
  padding: 1cqi;
}

.card-image {
  /* Width is quarter of container */
  width: 25cqw;
}

Practical Examples

Responsive Card Component

<div class="card-container">
  <article class="card">
    <img class="card-image" src="image.jpg" alt="">
    <div class="card-content">
      <h2 class="card-title">Card Title</h2>
      <p class="card-description">Description text goes here.</p>
      <button class="card-action">Action</button>
    </div>
  </article>
</div>
/* Base styles */
.card-container {
  container-type: inline-size;
  max-width: 600px;
}

.card {
  display: flex;
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.card-image {
  width: 150px;
  object-fit: cover;
}

.card-content {
  padding: 1.5rem;
  flex: 1;
}

.card-title {
  font-size: 1.5rem;
  margin: 0 0 0.5rem;
}

.card-description {
  color: #666;
  margin: 0 0 1rem;
}

/* Container queries for responsiveness */
@container (max-width: 400px) {
  .card {
    flex-direction: column;
  }
  
  .card-image {
    width: 100%;
    height: 150px;
  }
  
  .card-title {
    font-size: 1.25rem;
  }
  
  .card-action {
    width: 100%;
  }
}

@container (max-width: 250px) {
  .card-description {
    display: none;
  }
  
  .card-title {
    font-size: 1rem;
  }
}
<nav class="nav-container">
  <div class="nav-brand">Logo</div>
  <ul class="nav-links">
    <li><a href="#">Home</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Services</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>
.nav-container {
  container-type: inline-size;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  background: #fff;
  border-bottom: 1px solid #ddd;
}

.nav-links {
  display: flex;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* When container is small, switch to hamburger menu */
@container (max-width: 600px) {
  .nav-container {
    flex-wrap: wrap;
  }
  
  .nav-links {
    width: 100%;
    flex-direction: column;
    display: none;
  }
  
  .nav-links.open {
    display: flex;
    padding-top: 1rem;
  }
  
  /* Add hamburger trigger here */
}

/* Very small containers */
@container (max-width: 300px) {
  .nav-brand {
    font-size: 0.875rem;
  }
  
  .nav-links a {
    padding: 0.5rem;
    font-size: 0.875rem;
  }
}

Grid of Cards

<div class="grid-container">
  <!-- Multiple card containers -->
  <div class="card-wrapper">
    <article class="card">...</article>
  </div>
  <div class="card-wrapper">
    <article class="card">...</article>
  </div>
  <div class="card-wrapper">
    <article class="card">...</article>
  </div>
</div>
.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1rem;
}

.card-wrapper {
  /* Each card responds to its own container */
  container-type: inline-size;
}

@container (max-width: 200px) {
  .card {
    padding: 0.5rem;
  }
  
  .card-image {
    display: none;
  }
}

Container Style Queries

Container Style Queries allow styling based on computed styles of the container (experimental):

/* Query container's computed style */
@container style(color-scheme: dark) {
  .card {
    background: #1a1a1a;
    color: #fff;
  }
}

@container style(theme: primary) {
  .card {
    border-color: #007bff;
  }
}

Browser Support and Polyfills

/* Check for container query support */
@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
  
  @container (max-width: 400px) {
    .card {
      /* Styles for small containers */
    }
  }
}

/* Fallback for unsupported browsers */
@supports not (container-type: inline-size) {
  @media (max-width: 400px) {
    .card {
      /* Traditional media query fallback */
    }
  }
}

Polyfill Usage

<!-- CQ Fill Polyfill -->
<script type="module">
  import 'https://cdn.jsdelivr.net/npm/container-query-polyfill';
</script>

Best Practices

Do: Use Container Queries for Reusable Components

/* Good: Component is self-contained */
.product-card {
  container-type: inline-size;
}

@container (max-width: 300px) {
  .product-card {
    /* Adjusts based on its container */
  }
}

Don’t: Over-Nest Containers

/* Avoid: Too many nested containers */
.card-container {
  container-type: inline-size;
}

.card-container .inner {
  /* This could get complex */
  container-type: inline-size;
}

/* Better: Keep it simple */

Use Semantic Container Names

/* Good: Descriptive names */
@container sidebar (max-width: 200px) { }
@container main-content (max-width: 600px) { }
@container card (max-width: 400px) { }

/* Avoid: Generic names */
@container c1 (max-width: 200px) { }
@container c2 (max-width: 400px) { }

Combine with CSS Grid and Flexbox

.layout {
  display: grid;
  grid-template-columns: 250px 1fr;
  container-type: inline-size;
}

@container (max-width: 600px) {
  .layout {
    grid-template-columns: 1fr;
  }
}

Advanced Patterns

Container Query Ranges

/* Range queries */
@container (400px <= width < 600px) {
  .card {
    /* Medium size */
  }
}

@container (width >= 600px) {
  .card {
    /* Large size */
  }
}

@container (width < 400px) {
  .card {
    /* Small size */
  }
}

Querying Multiple Properties

/* Query both size and style (in supported browsers) */
.container {
  container-type: size;
  container-name: mycontainer;
}

@container mycontainer (min-width: 400px) and (min-height: 300px) {
  .card {
    /* Large enough container */
  }
}

Logical Properties

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

@container (inline-size > 400px) {
  /* Use logical properties for RTL support */
  .card {
    inline-size: 50%;
    margin-block: 1rem;
  }
}

Performance Benefits

flowchart TD
    subgraph Before["Traditional Approach"]
        M1[Media Query 1] --> R1[Re-render]
        M2[Media Query 2] --> R2[Re-render]
        M3[Media Query 3] --> R3[Re-render]
    end
    
    subgraph After["Container Queries"]
        C1[Container Query 1] --> P1[Local Reflow]
        C2[Container Query 2] --> P2[Local Reflow]
    end

Container queries can improve performance by:

  • Reducing unnecessary reflows
  • Enabling more targeted style changes
  • Allowing independent component rendering

External Resources

Conclusion

CSS Container Queries transform how we build responsive web applications by enabling truly component-based responsive design. Instead of thinking in terms of viewport sizes, you can now think in terms of component contexts.

Key takeaways:

  • Use container-type: inline-size to establish query containers
  • Use @container to define query rules
  • Container queries work alongside media queries, not as a replacement
  • They’re supported in all modern browsers

Start using container queries for reusable components like cards, navigation, and widgets that need to adapt to their container rather than the viewport.

Comments