Skip to content

Modern CSS Layout: Container Queries and Subgrid

A hands-on guide to responsive layouts with CSS container queries and subgrid: component-level responsiveness, alignment inheritance, card grids, dashboards.

4 min read
CSS layout grid showing container query breakpoints adapting components to their container size

Beyond Media Queries: Component-Level Responsiveness

Media queries respond to the viewport. Container queries respond to the parent container. This distinction transforms how we build reusable components. A card component that adapts to its container works correctly whether placed in a narrow sidebar, a two-column layout, or a full-width hero section—without modifying its CSS for each context.

Container queries and subgrid are the two most impactful CSS layout features since Grid itself. Together, they solve problems that have plagued responsive design for over a decade.

Container Queries Fundamentals

Container queries require declaring a containment context on a parent element, then writing queries against that container's dimensions.

csscss
/* ❌ Media queries — component responds to viewport, not context */
.card {
  display: flex;
  flex-direction: column;
}
@media (min-width: 768px) {
  .card {
    flex-direction: row;
  }
}
/* Problem: this card in a 300px sidebar still goes horizontal
   on a 768px viewport — wrong layout for its context */
 
/* ✅ Container queries — component responds to its container */
.card-container {
  container-type: inline-size;
  container-name: card-wrapper;
}
 
.card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
}
 
@container card-wrapper (min-width: 400px) {
  .card {
    flex-direction: row;
    align-items: center;
  }
 
  .card__image {
    flex: 0 0 40%;
    max-width: 200px;
  }
 
  .card__content {
    flex: 1;
  }
}
 
@container card-wrapper (min-width: 600px) {
  .card {
    padding: 1.5rem;
    gap: 1.5rem;
  }
 
  .card__title {
    font-size: 1.5rem;
  }
}

The card now adapts to whatever container it is placed in, regardless of viewport size. Place it in a narrow sidebar and it stacks vertically. Place it in a wide content area and it goes horizontal. No media queries needed.

Container Query Units

Container query units (cqi, cqb, cqw, cqh) let you size elements relative to the container's dimensions, not the viewport.

csscss
.card-container {
  container-type: inline-size;
}
 
.card__title {
  /* Font size scales with container width */
  font-size: clamp(1rem, 3cqi, 1.5rem);
}
 
.card__description {
  font-size: clamp(0.875rem, 2cqi, 1.125rem);
  line-height: 1.6;
}
 
.card__image {
  /* Image height proportional to container */
  height: clamp(120px, 30cqi, 250px);
  object-fit: cover;
  border-radius: 0.5rem;
}
htmlhtml
<!-- The same component works in any layout context -->
<div class="grid" style="grid-template-columns: 1fr 2fr 1fr;">
  <div class="card-container">
    <!-- Narrow: stacked, small text -->
    <article class="card">
      <img class="card__image" src="..." alt="..." />
      <div class="card__content">
        <h3 class="card__title">Article Title</h3>
        <p class="card__description">Description text...</p>
      </div>
    </article>
  </div>
  <div class="card-container">
    <!-- Wide: horizontal, larger text -->
    <article class="card">
      <img class="card__image" src="..." alt="..." />
      <div class="card__content">
        <h3 class="card__title">Article Title</h3>
        <p class="card__description">Description text...</p>
      </div>
    </article>
  </div>
  <div class="card-container">
    <!-- Narrow: stacked, small text -->
    <article class="card">
      <img class="card__image" src="..." alt="..." />
      <div class="card__content">
        <h3 class="card__title">Article Title</h3>
        <p class="card__description">Description text...</p>
      </div>
    </article>
  </div>
</div>

Subgrid: Alignment Inheritance

Subgrid solves the card grid alignment problem that has tormented frontend developers for years. When cards have different amounts of content, their internal elements (title, description, footer) misalign across the row. Subgrid lets children inherit the parent grid's tracks.

csscss
/* ❌ Without subgrid — card internals don't align across rows */
.product-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}
 
.product-card {
  display: flex;
  flex-direction: column;
}
/* Titles, descriptions, and prices at different heights
   depending on content length. Looks messy. */
 
/* ✅ With subgrid — card internals align perfectly */
.product-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}
 
.product-card {
  display: grid;
  grid-row: span 4; /* image, title, description, price */
  grid-template-rows: subgrid;
  gap: 0.75rem;
  border-radius: 0.75rem;
  overflow: hidden;
}
 
.product-card__image {
  aspect-ratio: 16 / 10;
  object-fit: cover;
  width: 100%;
}
 
.product-card__title {
  font-weight: 600;
  padding: 0 1rem;
}
 
.product-card__description {
  color: #666;
  font-size: 0.9rem;
  padding: 0 1rem;
}
 
.product-card__price {
  font-weight: 700;
  font-size: 1.25rem;
  padding: 0 1rem 1rem;
  align-self: end;
}

Every card in the row shares the same row tracks. If one card has a longer title, all cards in that row allocate the same space for titles. Descriptions align. Prices align. The grid handles it automatically.

Dashboard Layout with Container Queries

Dashboards are the ideal use case for container queries. Widgets need to adapt to their allocated space, which changes dynamically as the user resizes panels or collapses sidebars.

csscss
.dashboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1rem;
  padding: 1rem;
}
 
.widget {
  container-type: inline-size;
  container-name: widget;
  background: white;
  border-radius: 0.75rem;
  padding: 1.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
 
/* Compact widget — shows summary only */
@container widget (max-width: 350px) {
  .widget__chart {
    display: none;
  }
 
  .widget__header {
    flex-direction: column;
    gap: 0.5rem;
  }
 
  .widget__value {
    font-size: 1.5rem;
  }
}
 
/* Standard widget — shows chart and summary */
@container widget (min-width: 351px) and (max-width: 600px) {
  .widget__chart {
    height: 150px;
  }
 
  .widget__value {
    font-size: 2rem;
  }
}
 
/* Expanded widget — full detail view */
@container widget (min-width: 601px) {
  .widget__chart {
    height: 250px;
  }
 
  .widget__details {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
  }
 
  .widget__value {
    font-size: 2.5rem;
  }
}

Combining Container Queries with Subgrid

The real power emerges when container queries and subgrid work together. Container queries handle the layout adaptation; subgrid handles the alignment.

csscss
.feature-section {
  container-type: inline-size;
  container-name: features;
}
 
.feature-grid {
  display: grid;
  gap: 2rem;
}
 
/* Single column for narrow containers */
@container features (max-width: 500px) {
  .feature-grid {
    grid-template-columns: 1fr;
  }
}
 
/* Two columns with subgrid alignment */
@container features (min-width: 501px) and (max-width: 900px) {
  .feature-grid {
    grid-template-columns: repeat(2, 1fr);
  }
 
  .feature-item {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
  }
}
 
/* Three columns for wide containers */
@container features (min-width: 901px) {
  .feature-grid {
    grid-template-columns: repeat(3, 1fr);
  }
 
  .feature-item {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
  }
}
 
.feature-item__icon {
  font-size: 2rem;
}
 
.feature-item__title {
  font-size: clamp(1rem, 2.5cqi, 1.25rem);
  font-weight: 600;
}
 
.feature-item__description {
  color: #555;
  font-size: clamp(0.875rem, 2cqi, 1rem);
  align-self: start;
}

Style Queries for State-Based Styling

Container style queries let you respond to custom property values on a container, enabling state-driven component styling without JavaScript class toggling.

csscss
.theme-section {
  container-type: inline-size;
  --theme: light;
}
 
.theme-section[data-theme="dark"] {
  --theme: dark;
}
 
/* Style query responds to custom property */
@container style(--theme: dark) {
  .card {
    background: #1a1a2e;
    color: #eee;
    border-color: #333;
  }
 
  .card__title {
    color: #fff;
  }
 
  .card__description {
    color: #ccc;
  }
}
 
@container style(--theme: light) {
  .card {
    background: #fff;
    color: #333;
    border-color: #e0e0e0;
  }
}

Key Takeaways

Container queries and subgrid fundamentally change how we approach responsive design. Container queries move responsiveness from the viewport to the component level, making components truly reusable across different layout contexts. Container query units (cqi) enable fluid typography and sizing relative to the component's container.

Subgrid solves the card alignment problem by letting children inherit parent grid tracks. No more misaligned titles, descriptions, and prices across card rows. Combined with container queries, you get components that both adapt their layout to available space and align their internals with siblings.

These are not experimental features—they have broad browser support and are ready for production. Every new component you build should start with container queries instead of media queries.

Wilfredo Rujel

Wilfredo Rujel

Full Stack Software Engineer

Share this postX