CSS Container Queries: Practical Examples That Actually Make Sense


I’ve been waiting for container queries for years. Media queries were fine when layouts were simpler, but they always had a fundamental problem: they respond to the viewport, not the container. A card component doesn’t care how wide the browser window is. It cares how wide the space it’s sitting in is.

Container queries fix this. They’ve had solid browser support since late 2023, and by now there’s no reason not to use them in production. But most tutorials I’ve seen show trivial examples that don’t reflect real-world usage. Here’s what I’ve actually used them for in shipped projects.

The Basics

Container queries let you style an element based on the size of its parent container rather than the viewport. The syntax is straightforward:

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

/* Query the container's size */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

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

The container-type: inline-size declaration tells the browser to track the inline (horizontal) size of that element. Then @container queries check that size and apply styles accordingly.

You can also use container-type: size to track both dimensions, but inline-size is what you’ll use 90% of the time.

Example 1: The Card Component

This is the canonical use case, but let me show a real version instead of a simplified demo.

I built a dashboard where cards appear in different layouts: a three-column grid on the main dashboard, a two-column grid in a sidebar widget, and a single-column list in a mobile drawer. The same card component needed to look good in all three contexts.

With media queries, I’d need to know the breakpoints for each layout and coordinate the card’s responsive behaviour with the parent layout’s breakpoints. If the parent layout changed, the card’s media queries would break.

With container queries:

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

.card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
}

.card__image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 0.5rem;
}

.card__meta {
  font-size: 0.875rem;
  color: var(--text-muted);
}

@container (min-width: 350px) {
  .card {
    flex-direction: row;
    gap: 1rem;
  }

  .card__image {
    width: 120px;
    aspect-ratio: 1;
  }
}

@container (min-width: 500px) {
  .card__image {
    width: 180px;
  }

  .card__title {
    font-size: 1.25rem;
  }

  .card__excerpt {
    display: block; /* Hidden in smaller sizes */
  }
}

The card adapts to its container width. Drop it in a narrow sidebar? It stacks vertically. Put it in a wide content area? It goes horizontal with a larger image. No coordination with the parent layout needed.

Example 2: Navigation That Collapses Based on Available Space

I had a header with a logo, main nav links, and a user menu. On wide screens, everything sits in a row. On narrow screens, the nav links collapse into a hamburger menu.

The problem: the header sits inside different layout contexts across the app. On the main pages, it spans the full viewport width. On the settings page, it sits inside a narrower content panel because there’s a sidebar taking up space.

Media queries would require different breakpoints for each context. Container queries solve this naturally:

.header {
  container-type: inline-size;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
}

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

.menu-toggle {
  display: none;
}

@container (max-width: 600px) {
  .nav-links {
    display: none;
  }

  .menu-toggle {
    display: block;
  }
}

The nav collapses when the header’s container gets narrow, regardless of viewport width. If the sidebar makes the header narrower, the nav adapts. If the sidebar is closed and the header gets more room, the nav expands. Zero JavaScript. Zero viewport-dependent logic.

Example 3: Form Layout Adaptation

This one surprised me with how well it worked. I had a form with label-input pairs that I wanted to display as side-by-side (label left, input right) when there was room, and stacked (label above input) when space was tight.

.form-group-wrapper {
  container-type: inline-size;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 1rem;
}

.form-group label {
  font-weight: 600;
  font-size: 0.875rem;
}

@container (min-width: 500px) {
  .form-group {
    flex-direction: row;
    align-items: center;
    gap: 1rem;
  }

  .form-group label {
    width: 200px;
    flex-shrink: 0;
    text-align: right;
  }

  .form-group input,
  .form-group select,
  .form-group textarea {
    flex: 1;
  }
}

This form works in a full-width layout, a modal, a sidebar settings panel, or a narrow mobile view - all without any layout-specific CSS. The form figures out its own layout based on the space available.

Example 4: Typography Scaling

Container query units (cqi, cqw, cqh) let you size things relative to the container. I’ve used this for responsive typography inside components:

.hero-section {
  container-type: inline-size;
  padding: 2rem;
}

.hero-title {
  font-size: clamp(1.5rem, 5cqi, 3.5rem);
  line-height: 1.2;
}

.hero-subtitle {
  font-size: clamp(1rem, 2.5cqi, 1.5rem);
  line-height: 1.4;
}

The cqi unit is 1% of the container’s inline size. Combined with clamp(), you get typography that scales with the container while staying within readable bounds. This is much more useful than viewport units (vw) for components that don’t span the full viewport.

Gotchas

You can’t query a container and style the container itself. The @container query applies to children of the container, not the container element. This tripped me up initially. If you need the container itself to change, wrap it in another element and make that the container.

container-type: inline-size affects layout. Setting an element as a container creates a new formatting context, similar to overflow: hidden. This can occasionally break layouts that depend on margin collapsing or overflow behaviour. Test carefully.

DevTools support varies. Chrome DevTools shows container queries well, including highlighting which container is being queried. Firefox has similar support. Safari’s tools are still catching up as of early 2026.

Don’t overuse them. Container queries are great for reusable components that appear in multiple layout contexts. They’re overkill for elements that always appear in the same place. If a component only ever lives in one layout context, a regular media query or fixed sizing is simpler and clearer.

The Mental Shift

The biggest change with container queries isn’t syntactic - it’s conceptual. Media queries train you to think about responsive design globally: “at this viewport width, the layout does this.” Container queries let you think about responsive design locally: “this component adapts to its available space.”

That’s a much better mental model for component-based development. Your components become truly portable. Drop them anywhere, and they adapt. That’s how responsive design should have worked from the beginning.

Check the MDN container queries guide for the full spec details. Browser support is over 90% globally now, so there’s no reason to hold back.