Novo Livro!
Categorias
Projetos
CSS

Pare de tratar container queries como media queries

Pare de tratar container queries como media queries

As container queries têm suporte amplo nos navegadores e, mesmo assim, continuam pouco usadas e mal compreendidas. A seguir, como elas diferem das media queries, quando usar cada uma e como permitem que componentes reutilizáveis se adaptem ao espaço em que aparecem, sem depender da largura da tela.

O autor, Victor Ayomipo, admite que perdeu a notícia quando as CSS Container Queries chegaram aos navegadores. E, quando finalmente ouviu falar delas, o primeiro pensamento foi: “Para que exatamente eu preciso disso, se as media queries já existem?”

Ele não se orgulha dessa reação, sabendo o que sabe hoje, mas foi reconfortante descobrir que não estava sozinho. Na verdade, tem muita gente na mesma situação.

O que intriga é que container queries não são um recurso novo: o suporte nos navegadores está em torno de 94%. E, ainda assim, pouquíssima gente as usa de fato.

Logo no início, já mostramos, na prática, como é o funcionamento em nosso vídeo Container Queries para Web Design Responsivo de Verdade:

Segundo a pesquisa State of CSS, 86% dos devs conhecem container queries, mas só 41,4% realmente as utilizam. Pesquisas podem ter viés e não representar a área inteira, mas essa certamente é o melhor indicador disponível.

Kevin Powell também falou disso na SmashingConf Amsterdam 2026: a adoção de container queries tem sido péssima.

E isso é muito estranho, considerando que a capacidade de componentes se adaptarem ao tamanho do container externo esteve no topo de tantas listas de desejos de CSS ao longo dos anos.

Aqui mesmo no blog, em 2018, o assunto apareceu em Responsive Components: uma solução para container queries, quando o suporte nativo ainda nem existia.

Mais interessante do que saber quantas pessoas usam container queries é saber como elas as usam. Não dá para falar por todo mundo, mas, pelo que se vê por aí (e nas primeiras tentativas do Victor também), muita gente está usando container queries do jeito errado.

No fim das contas, o uso incorreto vem daquela primeira impressão: à primeira vista, elas parecem exatamente media queries. E, como parecem parecidas, é fácil presumir que servem a propósitos semelhantes e funcionam do mesmo jeito.

Não é o caso.

Media queries olham para fora

O viewport é um substituto. Sempre foi. Foram as media queries que criaram a ilusão de que a largura da tela, sozinha, é responsável por como apps responsivos se adaptam ao ambiente.

Vale a pergunta: ao escrever @media, o que se está perguntando ao navegador?

@media (min-width: 1024px) {
.card {
display: flex;
}
}

Para a maioria dos devs, a pergunta é: qual é a largura da tela agora? Só isso.

As media queries respondem isso muito bem, mas o que acontece quando esse componente .card é colocado numa célula de grid com 300px de largura, numa tela de desktop de 1920px?

A media query não se importa: ela faz o trabalho dela. O viewport continua com 1920px, então min-width: 1024px dispara e os estilos da query são aplicados, mesmo que o card só tenha 300px de espaço para trabalhar. No fim, tudo dentro do card se deforma, transborda ou fica espremido.

“Media queries são burras. Não burras como conceito, mas burras no sentido de que não sabem muita coisa. Na verdade, a maioria das pessoas presume que elas sabem mais do que realmente sabem.”

Kevin Powell

É comum pensar no design responsivo apenas como um sistema para atualizar layouts de página inteiros, como passar de duas colunas numa tela grande para uma coluna só numa tela pequena.

Container queries olham para dentro

As container queries são mais espertas que isso. Elas fazem os layouts responsivos dependerem mais do que acontece dentro de um componente do que do contexto externo, que não faz ideia do conteúdo do componente. A pergunta passa a ser algo como: “Quanto espaço está disponível para mim neste lugar específico, agora?”

Este é o mesmo exemplo de card da seção anterior, agora com uma container query:

.card-wrapper {
container-name: card;
container-type: inline-size;
}
@container card (min-width: 450px) {
.card {
display: flex;
flex-direction: row;
}
}

Isso muda tudo. O card não é influenciado pelo viewport: a única preocupação dele é saber se o wrapper pai do componente .card tem pelo menos 450px de espaço inline (ou seja, horizontal, num modo de escrita da esquerda para a direita).

Se a condição for verdadeira, o componente fica na horizontal; se não, volta ao display padrão, block.

Viewport vs Container, de Vayo.

Na figura abaixo, o mesmo card está em duas colunas de uma página: a principal e uma sidebar de 300px. Troque a query e arraste sobre a figura para mudar a viewport. Com @media, os dois cards mudam juntos em 1024px; com @container, cada um olha só para a própria coluna.

A marca que decide: 1024px na régua da viewport, ou 450px na régua de cada baiaOs cards: em linha, a imagem fica ao lado do texto; empilhados, em cima deleA página tem 16px de margem, uma coluna principal flexível e uma sidebar fixa de 300px, com 24px entre elas. Com a media query, os dois cards trocam juntos em 1024px: acima disso, o da sidebar fica em linha em 300px e o texto vira uma tira estreita; entre 806px e 1023px, o da coluna principal empilha mesmo tendo espaço para ficar em linha. Com a container query, cada card olha a própria baia: o da sidebar nunca passa de 300px e fica empilhado, e o da coluna principal fica em linha a partir de 450px de coluna, que é uma viewport de 806px.

Layouts “macro” vs. layouts “micro”

Um jeito bem interessante de distinguir @media e @container é pelo tipo de layout.

As media queries servem para o layout “macro”: elas olham para fora.

São coisas como a estrutura da página, o header que ocupa a janela inteira, o footer, o grid principal, as preferências do sistema (prefers-color-scheme) e as capacidades do dispositivo (telas touch). Ou seja, tudo o que vale para a estrutura da página como um todo.

Já as container queries servem para os layouts “micro”, isto é, a maior parte do que vive dentro do layout “macro”. Elas entram em cena quando um conteúdo precisa caber, de forma responsiva, em qualquer espaço que receber. Os componentes que vêm à mente são cards, widgets, formulários, navegação e assim por diante.

Em outras palavras, pense em “layout da página” vs. “layout do componente”. É a mesma virada descrita em O novo web design responsivo, quando container queries ainda eram só uma proposta.

Um elemento não deveria virar “tamanho tablet” num passe de mágica só porque a largura passou de um limite abstrato como 768px. Ele deveria trocar de layout quando tiver espaço suficiente para isso, seja num viewport de celular, seja dentro de uma sidebar no desktop.

Ainda não se convenceu? Sabia que existem mais de 2.300 tamanhos de viewport diferentes na web atual? Dá para dar conta de todos eles?

Não se trata de odiar media queries. É que, nesta era de responsividade e reuso de componentes, a lógica de layout está mais perto do container do que do viewport. Ao pensar em containers e componentes, quem determina o layout é o conteúdo, não o viewport.

É assim que deveria ser.

Exemplo: tipografia fluida dentro de um componente

Tipografia responsiva é um bom exemplo de algo para o qual muita gente sempre recorreu às media queries. Como existem unidades de CSS relativas ao tamanho do viewport (vw, vh e assim por diante), as media queries parecem ótimas para ajustar o tamanho da fonte de acordo com a tela do usuário.

.card-title {
font-size: clamp(100%, 1rem + 2vw, 24px);
}

Isso funciona até o mesmo componente ser levado para outro contexto, como uma sidebar, onde o viewport é totalmente irrelevante. Como a responsividade foi amarrada ao ponto de referência errado, a tipografia escalonada pode ficar grande ou pequena demais.

As container queries têm unidades próprias (cqi, cqw, cqb, entre outras). Dá para levar os componentes responsivos além combinando essas unidades com a função clamp() do CSS, usada para tipografia fluida que escala com o componente, e não com o viewport:

.card-title {
font-size: clamp(1rem, .5rem + 3cqi, 2rem);
}

Fluid Typography, de Vayo.

Com isso, todo o código fica contido no container específico daquele elemento.

Exemplo: detectando a quebra de linha do flexbox

Curiosamente, as container queries conseguem, de certa forma, detectar o estado do layout interno de um componente. Não é à prova de falhas, mas é algo que as media queries simplesmente não conseguem fazer.

Elas só observam a janela do navegador e são estruturalmente cegas a eventos internos de layout, como o momento em que itens flex quebram para uma nova linha. Vale investigar isso.

O flexbox é excelente para quebrar conteúdo em linhas (flex-wrap: wrap): os itens flex passam automaticamente para novas linhas quando o flex container não tem mais espaço para acomodá-los numa linha só. Mas o CSS, sozinho, não consegue saber quando essa quebra acontece.

Não existe uma pseudo-classe :wrapped nem uma media query como @media (flex-wrapped: true) que resolva isso.

Media queries só observam a janela do navegador, então não enxergam mudanças internas. E é basicamente isso que a quebra do flex é: uma mudança de estado na largura do próprio item.

Por exemplo, num menu horizontal alinhado com flexbox em que os itens devem mudar de estilo só quando quebram de linha, o caminho seria JavaScript, usando ResizeObserver para obter essa informação.

Mas, aninhando container queries dentro dos itens flex, dá para chegar a uma solução alternativa sem JavaScript, graças a uma técnica ensinada por Kevin Powell. A ideia central é deixar o item flex com flex-grow: 1 ao consultar o inline size do container.

Fluid Typography, de Vayo.
Estados normal e quebrado de dois itens responsivos. No estado normal, os itens ficam lado a lado, cada um com 50% da largura, e a query está desligada. No estado quebrado, ficam empilhados, cada um com 100% da largura do pai, e a query está ligada.

A lógica funciona assim:

  • Quando há espaço suficiente, os dois itens (.flex-item) ficam lado a lado, cada um com exatamente metade da largura do container pai.
  • Quando o espaço é limitado, o segundo item quebra para a linha seguinte.
  • Como flex-grow está ativo em cada item, os itens quebrados se esticam para preencher a maior parte da largura do pai.
  • Se o item também é um container, ele detecta o súbito aumento de largura e a query dispara.
/* O pai flex */
.flex-layout {
display: flex;
flex-wrap: wrap;
}
/* Registra um item flex como container */
.flex-item {
container-type: inline-size;
flex: 1 1 390px; /* Cresce para preencher o espaço, quebra em 390px */
}
/* Estilos padrão do card (estreito / lado a lado) */
.card {
display: flex;
flex-direction: column;
background: #f4f4f4;
}
/* Quando há espaço para uma linha inteira */
@container (min-width: 600px) {
.card {
flex-direction: row;
align-items: center;
background: #e2f0d9;
}
}

Funciona. Conforme o pai encolhe e os cards quebram em duas linhas, o item do card se expande, a container query dispara e aplica os estilos necessários.

Flex Wrap Detection Using Container Queries, de Vayo.

Container queries também têm efeitos colaterais

Container queries, como tudo, têm alguns efeitos colaterais e ressalvas que merecem atenção antes de recorrer a elas.

1. Um container exige um wrapper extra

Container queries precisam de algo parecido com uma relação pai-filho para funcionar como esperado. Imagine esta marcação:

<div class="card">
<div class="card-content">...</div>
</div>

Não dá para consultar o próprio componente .card para ajustar o .card-content, assim:

/* NÃO FUNCIONA */
.card {
container-name: card;
container-type: inline-size;
}
@container card (min-width: 400px) {
.card {
display: flex;
}
}

Isso não funciona porque um container não pode consultar a si mesmo. No último exemplo, consulta-se um container card e depois tenta-se ajustar o display desse mesmo container com base no tamanho dele. É um loop infinito.

Em vez disso, é preciso um wrapper adicional que torne o .card descendente do container:

<div class="cards">
<div class="card">
<div class="card-content">...</div>
</div>
</div>

A partir daí, dá para consultar o container .cards e ajustar o layout do .card:

.cards {
container-name: cards;
container-type: inline-size;
}
@container cards (min-width: 400px) {
.card {
display: flex;
}
}

Nas media queries isso não importa, porque @media não se preocupa com qual elemento é estilizado dentro do bloco: a única preocupação é o viewport, que está sempre disponível. Então basta colocar uma condição em qualquer elemento e pronto.

2. Consultar o size de um container pode colapsar o layout

Isso acontece ao consultar o size do container (ou seja, o tamanho de bloco, vertical) em vez do inline-size:

/* Colapsa para 0px mesmo com conteúdo dentro */
.hero-banner {
container-type: size;
}

Por quê? Porque o navegador calcula as dimensões do container sem olhar para os filhos. Se o .hero-banner não receber uma height explícita (ou min-height, ou aspect-ratio), o navegador define a altura como 0px.

Por isso, costuma ser melhor consultar um container pelo inline-size. A não ser que seja realmente necessário consultar o tamanho de bloco do container.

Media queries não são afetadas por isso, já que tratam a altura do mesmo jeito que @media (min-height: ...) faz: perguntam ao viewport e seguem em frente.

3. Queries não aceitam custom properties

Outra limitação das container queries: não dá para consultar o valor de uma custom property.

:root {
--breakpoint-lg: 1600px;
}
/* NÃO FUNCIONA */
@container (min-width: var(--breakpoint-lg)) {
/* ... */
}

Isso acontece porque custom properties dependem de valores que descem em cascata pela árvore do DOM. Uma container query que depende de uma custom property poderia alterar essa mesma custom property. E isso pode ficar complicado rapidamente:

:root {
--breakpoint-lg: 1600px;
}
/* NÃO FUNCIONA */
@container cards (min-width: var(--breakpoint-lg)) {
.card {
--breakpoint-lg: 1000px;
}
}

Quando usar media queries e quando usar container queries

Isso não significa que algum projeto deva trocar @media por @container em tudo. As media queries continuam tendo um papel importante em layouts responsivos. A questão é entender a separação de responsabilidades.

Container queries fazem sentido quando um componente é usado em mais de um contexto de layout. Por exemplo, um elemento .card pode viver num grid de largura total ou numa sidebar mais estreita.

Nesse caso, o ideal é que o conteúdo do componente determine quando ele se ajusta, e não uma media query olhando para o viewport externo.

Já as media queries servem para quando um componente existe apenas no nível da página. Seria algo como uma navegação principal que fica sempre no topo da página.

Ela é influenciada diretamente pelo tamanho do viewport, então o viewport é uma referência confiável para saber quando a navegação precisa se ajustar. De novo: tudo se resume a layout “macro” versus layouts “micro”.

Este é o diagrama sugerido para decidir qual tipo de query usar:

Fluxograma para escolher entre media queries e container queries. A primeira pergunta é se o componente é reutilizável ou pode viver em vários contextos. Se sim: depende de preferências globais do usuário ou de informações do hardware, como movimento ou dark mode? Se depender, use @media (prefers-*). Se não: precisa detectar um estado, como stuck ou scrollable? Se precisar, use @container (scroll-state). Se não: precisa do tamanho de bloco vertical, a altura? Se precisar, use container-type: size; se não, container-type: inline-size. Se o componente não for reutilizável: o estilo depende da página como um todo ou do viewport? Se depender, use @media. Se não: é estático, vive num único lugar e com largura fixa para sempre? Se for, use CSS padrão ou o dimensionamento intrínseco de flex e grid; se não for viewport nem estático, use @container.

Conclusão

No fim das contas, o motivo central de container queries parecerem tão semelhantes a media queries é simplesmente a familiaridade. Elas não são exatamente “novas”, mas são bem menos compreendidas e adotadas que as media queries.

Só que as media queries têm suas próprias limitações; se não tivessem, não haveria necessidade de container queries para preencher essas lacunas.

O que se tem é um recurso mais eficaz para detectar quando o contexto de um componente específico muda, e um meio de ajustar os estilos com base no conteúdo dele, como deve ser quando esse componente pode existir em vários contextos. (gg, yk)