container

An at-rule that switches the styles of elements inside a container based on the container's width. Replaces the JavaScript that watched widths with ResizeObserver and toggled classes with plain CSS.

Modern approach

Name the wrapper as a container and switch styles on its width. Because the switch depends on where the component is placed rather than on the viewport, the same component works as is in a sidebar or in the main column.

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

@container card (width >= 400px) {
  .card {
    display: grid;
    grid-template-columns: 120px 1fr;
  }
}
<div class="card-wrapper">
  <article class="card">
    <img src="thumbnail.jpg" alt="" />
    <div>...</div>
  </article>
</div>

Legacy approach

Media queries only know the viewport width, so switching on the width of where a component is placed required JavaScript. ResizeObserver watched the wrapper’s width and added a class to the card at 400px or wider.

.card.is-wide {
  display: grid;
  grid-template-columns: 120px 1fr;
}
<div class="card-wrapper">
  <article class="card">
    <img src="thumbnail.jpg" alt="" />
    <div>...</div>
  </article>
</div>
const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const card = entry.target.querySelector('.card')
    card.classList.toggle('is-wide', entry.contentRect.width >= 400)
  }
})

for (const wrapper of document.querySelectorAll('.card-wrapper')) {
  observer.observe(wrapper)
}

Things to watch when replacing

  • @container styles never apply to the container itself. Put a wrapper around the element you want to switch, and make the wrapper the container.
  • An element with inline-size no longer takes its width from its content. On an inline-block or a flex item sized by its content, the width becomes 0. Use it on elements whose width comes from outside, such as block elements.
  • container-type: size also stops the height from coming from the content. On elements without an explicit height, use inline-size.
  • An unnamed @container queries the nearest container. When containers are nested, it can switch on the wrong width, so name the container you mean.
  • Units based on the container’s width (such as cqi) are also available. They let font sizes and spacing follow the width of where the component is placed.

Fallback

All five target browsers support it, so you normally don’t need to keep the old technique. If the narrow layout (stacked) is the base style, browsers without support simply keep it stacked. Only if those browsers also need the side-by-side layout, keep the legacy CSS and run the JavaScript only in browsers that don’t support container queries.

// Add the legacy class only in browsers that don't support container queries
if (!CSS.supports('container-type', 'inline-size')) {
  const observer = new ResizeObserver((entries) => {
    for (const entry of entries) {
      const card = entry.target.querySelector('.card')
      card.classList.toggle('is-wide', entry.contentRect.width >= 400)
    }
  })
  for (const wrapper of document.querySelectorAll('.card-wrapper')) {
    observer.observe(wrapper)
  }
}

Browser Support

  • Chrome

    v105+
    2022-09-02
  • Edge

    v105+
    2022-09-01
  • Safari

    v16+
    2022-09-12
  • Safari (iOS)

    v16+
    2022-09-12
  • Firefox

    v110+
    2023-02-14

Search by feature name or code

e.g. aspect-ratio, :has(), padding-top

Try a different word or a property name.

Could not load search.