Você está montando um dashboard: cabeçalho, barra lateral, área de conteúdo principal, rodapé. Você recorre ao Flexbox porque é a ferramenta de layout que conhece, e vinte minutos depois tem três containers display: flex aninhados, uma barra lateral que não mantém a largura sem um número mágico, e um rodapé que flutua na metade da página quando o conteúdo é curto. Nada disso é um bug do Flexbox. Você está usando uma ferramenta unidimensional para um problema bidimensional.

O Flexbox distribui espaço ao longo de uma única linha, seja ela uma row ou uma column. O Grid posiciona itens em linhas e colunas ao mesmo tempo, e essas linhas e colunas se relacionam entre si. A maior parte da confusão entre Grid e Flexbox vem de ignorar essa diferença.

Flexbox é unidimensional, Grid é bidimensional

Os itens flex vivem em um único eixo, definido por flex-direction. Com flex-direction: row, cada filho se alinha da esquerda para a direita (ou da direita para a esquerda, com row-reverse); com column, eles se empilham de cima para baixo. Existe um eixo cruzado, e align-items atua sobre ele. Mas o Flexbox nunca faz um item de uma linha se alinhar com um item de outra. Mesmo quando os itens quebram para uma nova linha, cada linha quebrada é organizada de forma independente.

1
2
3
4
5
6
7
.toolbar {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

Os itens do Grid ficam em linhas e colunas que você define de antemão, e é possível posicionar e dimensionar cada item em relação aos dois eixos ao mesmo tempo.

1
2
3
4
5
6
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
}

1fr é uma unidade fracionária. O navegador subtrai primeiro as faixas fixas (240px) e só depois divide o que resta entre as faixas fr. Por isso grid-template-columns: 1fr 2fr dá à segunda coluna o dobro da parcela da primeira sobre o espaço que sobrou, não o dobro da largura total.

Como construir uma navbar com Flexbox

Uma navbar é o exemplo clássico de layout unidimensional: um logo, alguns links e um botão em uma única linha, com o espaço distribuído entre eles.

1
2
3
4
5
6
7
8
9
<nav class="navbar">
  <div class="logo">Acme</div>
  <ul class="links">
    <li><a href="/pricing">Pricing</a></li>
    <li><a href="/docs">Docs</a></li>
    <li><a href="/blog">Blog</a></li>
  </ul>
  <button class="cta">Sign up</button>
</nav>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
}

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

justify-content: space-between fixa o logo na borda esquerda e o botão na direita, com a lista de links entre os dois. align-items: center mantém os três elementos centralizados verticalmente, mesmo que o logo seja mais alto que o botão. A lista de links é o seu próprio container flex, então o espaçamento vem do gap e você nunca precisa tocar em uma margin.

Reduza a viewport e nada quebra, porque flex-wrap tem nowrap como padrão: os itens encolhem até a largura mínima do conteúdo e depois vazam da navbar. Adicione flex-wrap: wrap e os itens que não cabem caem para uma segunda linha. Essa segunda linha é organizada de forma independente e não se alinha com nada da primeira. Para uma navbar, tudo bem. Para um layout em que as linhas precisam se alinhar entre si, esse é o sinal de que você chegou ao limite do que o Flexbox resolve.

Como construir um layout de dashboard com CSS Grid

Áreas de grid nomeadas permitem ler o layout de uma página sem precisar traduzir números de linha e coluna na cabeça.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.dashboard {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 64px 1fr 48px;
  grid-template-areas:
    "sidebar header"
    "sidebar main"
    "sidebar footer";
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

Cada nome em grid-template-areas precisa formar um retângulo, e as strings funcionam como um desenho da página: basta olhar o CSS para ver que a barra lateral ocupa as três linhas. Ela tem 240px fixos, o cabeçalho e o rodapé recebem alturas explícitas, e main ocupa o que sobra porque sua linha é 1fr. O rodapé fica na base da viewport mesmo em uma página curta, sem positioning absoluto e sem JavaScript, porque min-height: 100vh no container somado a uma linha 1fr já garante isso.

A versão em Flexbox da mesma página exige dois containers: uma row flex para a barra lateral mais o conteúdo, e depois uma column flex dentro do conteúdo para cabeçalho, main e rodapé, com flex: 1 em main para empurrar o rodapé para baixo. Funciona, mas você está empilhando dois layouts unidimensionais para simular um bidimensional, e qualquer mudança na estrutura da página obriga a tocar nos dois.

Um grid de cards responsivo sem media queries

Esse padrão sozinho substitui boa parte do boilerplate de media queries: um grid de cards que adiciona ou remove colunas conforme a viewport muda.

1
2
3
4
5
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
}

repeat(auto-fit, minmax(220px, 1fr)) diz ao navegador para encaixar o máximo de colunas de pelo menos 220px que a linha permitir, e depois esticá-las até preencher o espaço. Um container de 1400px fica com cinco colunas de cerca de 260px; um celular fica com uma única coluna de largura total. Nenhum breakpoint em lugar nenhum.

auto-fit e auto-fill só se comportam de forma diferente quando você tem menos cards do que caberiam em uma linha cheia. auto-fit colapsa as faixas vazias, então os cards reais se esticam por toda a linha. auto-fill mantém as faixas vazias, então os cards ficam com a largura que teriam em uma linha completa e o resto fica em branco. Use auto-fill quando a largura do card precisa ficar consistente independente da quantidade, como em um grid de produtos onde três resultados não deveriam inflar para um terço da página cada um. Use auto-fit quando uma linha pela metade vazia parecer pior do que cards mais largos, como em um dashboard com dois ou três blocos de resumo.

Como usar Grid e Flexbox juntos

Raramente você escolhe um dos dois para a página inteira. O Grid cuida do esqueleto; o Flexbox cuida do conteúdo unidimensional dentro de cada região.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr;
}

.card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

Cada .card fica em uma célula do grid e se estica até a altura dessa célula, já que os itens de grid se esticam por padrão. Dentro dele, cabeçalho, corpo e rodapé formam uma única coluna, então justify-content: space-between coloca o rodapé no fim de cada card, não importa quanto texto o corpo tenha. Essa é a regra prática: Grid para qualquer coisa que precise se relacionar com uma linha e uma coluna ao mesmo tempo, Flexbox para o conteúdo que corre em uma única linha, aninhado onde o Grid o colocou.

Se você usa o Grid para alternar entre uma visualização em cards e uma em lista, guarde a escolha do visitante no localStorage. É uma única chamada de setItem, e a escolha sobrevive a reloads e abas fechadas sem precisar de uma ida ao servidor.

Quando usar subgrid para grids aninhados

Um grid dentro de um item de grid começa seu próprio conjunto de faixas, sem relação com as do pai. Isso quebra assim que você quer que as linhas internas de um card (imagem, título, descrição, preço) se alinhem com as de uma fileira de cards vizinhos. Defina grid-template-rows: subgrid (ou grid-template-columns: subgrid) no grid aninhado e ele reaproveita as faixas do pai em vez de definir novas, então a linha do título de cada card se alinha com a dos vizinhos mesmo que cada card continue sendo seu próprio container de grid. O subgrid funciona em todos os navegadores principais atuais, então você pode usá-lo direto em vez de forçar alturas iguais com valores fixos.

Quando usar Flexbox mesmo quando o Grid também resolveria

O Grid consegue organizar uma única linha com grid-auto-flow: column. Mas, para conteúdo que de fato é unidimensional, o Flexbox tem algo que o Grid não tem: flex-grow e flex-shrink deixam cada item reclamar mais ou menos do espaço restante com base no próprio conteúdo. É isso que você quer em uma barra de busca que deve ocupar o espaço que um botão e um ícone de largura fixa deixarem sobrando. Prefira o Grid quando linhas e colunas precisam se relacionar; use o Flexbox quando estiver organizando uma única linha e quiser crescimento e encolhimento por item, não apenas alinhamento.

O erro também acontece no sentido contrário. É comum construir um layout de várias colunas com flex-wrap: wrap mais uma width em porcentagem em cada item, para simular um grid. Isso funciona até a última linha ter menos itens que as demais. Nesse ponto, space-between manda os dois últimos itens para as bordas opostas, ou flex-grow os estica mais largos que tudo acima. O navegador continua distribuindo itens ao longo de linhas, não preenchendo um grid. Se você está contando colunas, use Grid.

Se ainda precisar de JavaScript no resize, por exemplo para reposicionar um tooltip ao lado de um card, aplique debounce no handler em vez de rodá-lo em cada evento de resize disparado pelo navegador.

Grid ou Flexbox: como decidir

Uma pergunta resolve a escolha: os itens têm linhas e colunas que precisam se alinhar entre si, ou é uma única linha de itens?

SituaçãoUse
Navbar, grupo de botões, toolbarFlexbox
Esqueleto da página: cabeçalho, barra lateral, main, rodapéGrid
Grid responsivo de cards ou produtosGrid (auto-fit/auto-fill + minmax)
Centralizar um elemento, ou espaçar alguns poucos de forma uniformeFlexbox
Layout de formulário com labels e inputs alinhadosGrid
Conteúdo dentro de uma única célula de grid (cabeçalho/corpo/rodapé de um card)Flexbox, aninhado dentro do grid

Na próxima vez que você abrir uma stylesheet para um layout novo, desenhe as caixas primeiro. Se as linhas que você traçar se cruzarem, é Grid. Se cada caixa fica em uma única linha, é Flexbox.

Artigos relacionados