CSS Container Queries na Prática: Componentes Responsivos Sem JavaScript
O problema que media queries nunca resolveram
Um card de produto precisa funcionar em três contextos: sidebar de 280px, grid de duas colunas e hero de largura total. Com media queries, você escreve breakpoints baseados na viewport. O card não sabe se está numa sidebar ou num grid: ele só sabe a largura da janela do navegador.
O resultado é previsível: você acaba criando variantes com classes CSS (.card--small, .card--large) ou, pior, usando ResizeObserver em JavaScript para medir o container pai e aplicar classes dinamicamente. Container queries resolvem isso na camada certa: o componente reage ao espaço que o container pai oferece, não à viewport.
Como container queries funcionam por baixo
O mecanismo depende de dois conceitos: containment context e container query.
Primeiro, você declara um elemento como container de consulta. Isso instrui o navegador a rastrear as dimensões desse elemento para que filhos possam consultá-las. Segundo, você escreve regras @container que funcionam como media queries, mas consultam o container ancestral mais próximo.
/* Declara o wrapper como container nomeado */
.product-list {
container-type: inline-size;
container-name: product-list;
}
/* O card filho consulta o container, não a viewport */
@container product-list (min-width: 600px) {
.product-card {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1.5rem;
}
}
container-type: inline-size ativa containment apenas no eixo inline (horizontal em LTR). Existe size para ambos os eixos, mas raramente você precisa dele: containment em ambos os eixos exige que o elemento tenha altura explícita, o que quebra layouts com conteúdo dinâmico.
Container query vs. media query: quando usar cada uma
| Critério | Media query | Container query |
|---|---|---|
| Referência de medida | Viewport do navegador | Elemento container pai |
| Caso de uso principal | Layouts de página (header, grid global, nav) | Componentes reutilizáveis (cards, widgets, forms) |
| Composição | Não compõe: mesmo breakpoint para todos os contextos | Compõe: cada instância reage ao próprio container |
| Suporte (2024+) | Universal | 96%+ dos navegadores (baseline desde dezembro 2023) |
| Eixo vertical | Funciona com min-height | Requer container-type: size e altura explícita |
| Performance | Sem custo extra de layout | Custo marginal de containment (imperceptível em cenários reais) |
A regra prática: use media queries para o layout da página e container queries para componentes que vivem dentro desse layout. As duas coexistem sem conflito.
Card de produto responsivo ao container
Este é o caso de uso mais comum. O card empilha imagem sobre texto quando o container é estreito e muda para layout horizontal quando há espaço.
/* container.css */
.card-container {
container-type: inline-size;
container-name: card-wrapper;
}
.product-card {
display: flex;
flex-direction: column;
border: 1px solid hsl(220 10% 90%);
border-radius: 0.5rem;
overflow: hidden;
}
.product-card__image {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
.product-card__body {
padding: 1rem;
}
.product-card__title {
font-size: 1rem;
margin: 0 0 0.5rem;
}
/* Quando o container tem pelo menos 500px, layout horizontal */
@container card-wrapper (min-width: 500px) {
.product-card {
flex-direction: row;
}
.product-card__image {
/* Limita a imagem a 40% do espaço horizontal para
manter proporção legível no texto ao lado */
width: 40%;
aspect-ratio: 1 / 1;
}
.product-card__title {
font-size: 1.25rem;
}
}
/* Container largo: card vira destaque com tipografia maior */
@container card-wrapper (min-width: 800px) {
.product-card__title {
font-size: 1.5rem;
}
.product-card__body {
padding: 1.5rem 2rem;
}
}
<!-- O mesmo card funciona em qualquer contexto sem classes extras -->
<div class="card-container" style="width: 300px;">
<article
---
Leia o artigo completo em [https://www.vivodecodigo.com.br/react/css-container-queries-componentes-responsivos-sem-javascript](https://www.vivodecodigo.com.br/react/css-container-queries-componentes-responsivos-sem-javascript)