# Equilibrando itens flex com flex-wrap: balance

> Como o flex-wrap: balance distribui itens flex por igual entre as linhas, quatro jeitos de evitar o item órfão sem ele e o novo flex-line-count.

URL: https://dpw.dev/css/flex-wrap-balance/  
Publicado em: 2026-10-15  
Categoria: css  
Tags: flexbox, layout  
Autor: Tárcio Zemel

Quando itens flex quebram linha, é comum um deles sobrar sozinho na última linha. O **`flex-wrap: balance`** resolve isso distribuindo os itens por igual entre as linhas, sem media query nenhuma.

> Artigo baseado em [Balancing flex items with flex-wrap: balance](https://ishadeed.com/article/flex-wrap-balance/).

Aqui estão o problema em demonstrações interativas, quatro formas de contorná-lo enquanto o recurso só existe no Chrome e o `flex-line-count`, que define um número mínimo de linhas e permite até um layout masonry.

## Introdução

Ao trabalhar com flexbox e quebra de linha ativada, é comum chegar a uma situação em que os itens quebram linha, mas, em algum momento, só um deles vai para a linha de baixo: um item órfão.

Muitas vezes, quem desenha ou desenvolve a interface recorre a uma media query para evitar esse comportamento, o que está longe do ideal e quebra fácil se o conteúdo mudar.

Um exemplo mostra o problema.

```css
.list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}
```

Esta é a demonstração. Todas as demonstrações deste artigo mudam de largura pelo controle deslizante ou arrastando o canto inferior direito do quadro tracejado:

> **Demonstração interativa:** lista com flex-wrap: wrap e um item órfão. Disponível na versão HTML desta página.

O último item foi sozinho para uma nova linha. Não parece certo.

## flex-wrap: balance

Felizmente, agora existe o `flex-wrap: balance`, disponível a partir do Chrome 150. Com ele, o resultado fica muito mais equilibrado.

Ligue e desligue o `flex-wrap: balance` para ver como ele funciona bem (só no Chrome).

> **Demonstração interativa:** liga e desliga flex-wrap: balance na mesma lista. Disponível na versão HTML desta página.

<figure>
  <video controls muted playsinline preload="metadata" width="1000" height="352">
    <source src="/images/blog/flex-wrap-balance/gravacao-flex-wrap-balance.webm" type="video/webm" />
  </video>
  <figcaption>Gravação da demonstração acima no Chrome, para quem lê num navegador sem suporte a <code>flex-wrap: balance</code>.</figcaption>
</figure>

Antes de usar o `flex-wrap: balance`, vale resolver um exemplo comum sem ele, já que o recurso ainda só existe no Chrome.

## Exemplo real: a seção de redes sociais da Buffer.com

Essa seção tem dois elementos principais:

- O título
- Uma lista de ícones de redes sociais

Veja:

![Seção da Buffer com o título Connect your favorite accounts à esquerda e uma fileira de ícones de redes sociais à direita.](/images/blog/flex-wrap-balance/secao-conexao-buffer.webp)

À primeira vista, parece tudo certo. Mas, ao redimensionar a viewport, o layout começa a quebrar. Veja o vídeo abaixo:

<figure>
  <video autoplay loop muted playsinline controls width="1000" height="470">
    <source src="/images/blog/flex-wrap-balance/secao-conexao-buffer-redimensionando.webm" type="video/webm" />
  </video>
</figure>

O wrapper dos ícones é um flex container com `justify-content: space-between`. É isso que faz os ícones quebrarem linha e deixa dois ícones numa mesma linha, longe um do outro.

Dificilmente isso é intencional.

![A mesma seção da Buffer em cinco larguras: conforme ela estreita, os ícones que sobram ficam espalhados na segunda linha, distantes uns dos outros.](/images/blog/flex-wrap-balance/secao-conexao-buffer-quebrada.webp)

Hora de corrigir. A seção foi replicada nas demonstrações abaixo para testar como resolver esse problema de layout.

Para caber na coluna de texto, nas réplicas o título fica sempre acima dos ícones. Abaixo de cada uma aparece a largura do contêiner dos canais, que é a medida que as container queries comparam com os valores do CSS.

### Solução 1: container queries

O primeiro passo é deixar o flex container quebrar linha normalmente e usar `justify-content: center` para centralizar os ícones.

```css
.channels {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
```

> **Demonstração interativa:** ícones centralizados com flex-wrap: wrap. Disponível na versão HTML desta página.

A ideia é esta:

- Definir um contêiner para os canais.
- Calcular a largura de cada canal mais o espaço entre eles.
- Se a largura do contêiner for menor ou igual à largura calculada dos canais, o layout é forçado a quebrar linha.

Essa solução funciona em qualquer navegador com suporte a container queries. A ideia é definir um `max-width` nos contêineres pequenos para mostrar um número predefinido de canais por linha, aqui 5.

```css
.section-wrapper {
  container-type: inline-size;
  container-name: demo;
}

.section {
  --size: 44px;
  --gap: 1rem;
  --count: 8;
  --width: calc(var(--size) * var(--count) + var(--gap) * (var(--count) - 1));
  --items-row: 5;
  --max-width: calc(
      var(--size) * var(--items-row) + var(--gap) * (var(--items-row) - 1)
    );
}

.channels {
  max-width: var(--max-width);

  /* --width é 464px. Não dá para usar variável CSS dentro de uma media query. */
  @container (width >= 464px) {
    max-width: initial;
    background-color: rgba(from deeppink r g b / 0.3);
  }
}
```

**Mexa no controle de largura, ou arraste o canto do contêiner, e veja como o layout muda.** O fundo rosa aparece quando a container query passa a valer, ou seja, quando o contêiner dos canais chega a 464px.

> **Demonstração interativa:** solução 1, max-width com container query. Disponível na versão HTML desta página.

### Solução 2: max-width com um clamp condicional

Nesta solução, o elemento pai vira um contêiner e o `max-width` cuida do layout.

```css
.section-wrapper {
  container-type: inline-size;
  container-name: demo;
}

.section {
  --size: 44px;
  --gap: 1rem;
  --count: 8;
  --items-row: 5;
  --width: calc(var(--size) * var(--count) + var(--gap) * (var(--count) - 1));
  --min: calc(
    var(--size) * var(--items-row) + var(--gap) * (var(--items-row) - 1)
  );
  --max-width: clamp(var(--min), (100cqw - var(--width)) * 999, 100%);

  .channels {
    max-width: var(--max-width);
  }
}
```

É preciso saber de antemão:

- O tamanho de cada canal (44px)
- O espaço entre eles (gap)
- A quantidade total
- O número de itens por linha

A partir disso, dá para calcular o número mínimo de itens por linha e a largura máxima.

Esta é a demonstração funcionando:

> **Demonstração interativa:** solução 2, max-width com clamp condicional. Disponível na versão HTML desta página.

Para quem ficou curioso, essa solução usa um clamp condicional, assunto de um <a href="https://ishadeed.com/article/conditional-border-radius/" target="_blank" rel="noopener noreferrer">artigo anterior</a>.

> **Nota:** O truque está no meio do `clamp()`. Quando o contêiner é mais largo que a lista inteira, `100cqw - var(--width)` dá positivo e, multiplicado por 999, passa de 100%: o `clamp()` fica no teto e a lista ocupa tudo. Quando é mais estreito, a conta dá negativo e o `clamp()` fica no piso, a largura de cinco canais.
>
> Mude a quantidade de canais na demonstração e veja o ponto de virada acompanhar, coisa que a solução 1, com os 464px fixos, não faz. Para relembrar como a função funciona, veja <a href="/css/textos-responsivos-com-css-clamp/" target="_blank">textos responsivos com CSS clamp()</a>.

### Solução 3: display contents

Nesta solução, os itens são divididos em dois grupos. Em tamanhos menores, os grupos são achatados com `display: contents`. Em tamanhos maiores, os grupos aparecem para imitar a quebra equilibrada.

```html
<div class="channels">
  <div class="group-1">
    <!-- primeira metade dos canais -->
  </div>
  <div class="group-2">
    <!-- segunda metade dos canais -->
  </div>
</div>
```

```css
.channels {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;

  > div {
    /* Ignora os grupos em tamanhos pequenos. */
    display: contents;

    @container (width >= 500px) {
      display: inherit;
      flex-wrap: inherit;
      justify-content: inherit;
      gap: inherit;
      background-color: rgba(from deeppink r g b / 0.3);
    }
  }
}
```

> **Demonstração interativa:** solução 3, dois grupos com display: contents. Disponível na versão HTML desta página.

Funciona, mas não é a solução mais elegante. Ainda assim, é bem melhor do que usar só `justify-content: space-between`.

Para saber mais, há um artigo sobre <a href="https://ishadeed.com/article/display-contents/" target="_blank" rel="noopener noreferrer">display: contents</a>.

### Solução 4: um item flex no meio

Esta solução não funcionou bem, mas vale mostrar mesmo assim. A ideia é adicionar um pseudo-elemento e usar a propriedade `order`.

O pseudo-elemento é forçado a ocupar a linha inteira e vai para o meio da lista com a ajuda da propriedade `--count`.

```css
ul {
  &:before {
    content: "";
    order: calc(var(--count) / 2 + 1);
    flex: 0 0 100%;
  }
}

li {
  &:nth-child(1) {
    order: 1;
  }
  /* e assim por diante... */
}
```

Na demonstração, o pseudo-elemento aparece como uma linha rosa. Estreite o contêiner até caberem só três ícones por linha para ver onde a solução falha: a quebra no meio continua lá, e os órfãos voltam.

> **Demonstração interativa:** solução 4, pseudo-elemento que quebra a lista no meio. Disponível na versão HTML desta página.

### Solução 5: flex-wrap balance

Depois de todas essas soluções, veja como é simples usar o `flex-wrap: balance`.

```css
.channels {
  display: flex;
  flex-wrap: balance;
  justify-content: center;
  gap: 1rem;
}
```

> **Demonstração interativa:** solução 5, flex-wrap: balance. Disponível na versão HTML desta página.

Só isso!

## flex-line-count

A pesquisa sobre `flex-wrap: balance` revelou outra propriedade CSS nova: `flex-line-count`.

Ela define um número mínimo de linhas para os itens flex que quebram. Mesmo que haja espaço de sobra e os itens não precisem quebrar, **dá para forçar um número mínimo de linhas**.

```css
.list {
  display: flex;
  flex-wrap: balance;
  flex-line-count: 2;
  gap: 0.5rem;
}
```

Aqui, a quebra começa com pelo menos duas linhas, mesmo com espaço suficiente para os itens ficarem numa só.

> **Demonstração interativa:** flex-line-count define o mínimo de linhas. Disponível na versão HTML desta página.

Parece mágica, e deve abrir um mundo de casos de uso que antes não eram possíveis. Como tirar proveito disso?

### Demonstração de navegação

Neste exemplo, há um cabeçalho em que a navegação deve sempre ocupar duas linhas. Normalmente, a navegação seria limitada assim:

- Definindo uma largura máxima.
- Definindo um grid com largura fixa para a navegação.
- E outras formas…

A demonstração a seguir usa a clássica solução com `max-width`, marcada pelo contorno tracejado. Veja:

```css
.nav ul {
  max-width: 320px;
  margin-inline: auto;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}
```

> **Demonstração interativa:** navegação limitada por max-width. Disponível na versão HTML desta página.

Funciona, mas perde-se a liberdade de estilizar o wrapper da navegação. Se for preciso um fundo colorido, por exemplo, ele fica restrito ao `max-width`.

> **Demonstração interativa:** o fundo da navegação fica preso ao max-width. Disponível na versão HTML desta página.

Com o `flex-line-count`, dá para definir o número mínimo de linhas dos itens que quebram, e a largura do wrapper da navegação continua a mesma.

```css
.nav ul {
  display: flex;
  justify-content: center;
  flex-wrap: balance;
  flex-line-count: 2;
  gap: 0.5rem;
}
```

> **Demonstração interativa:** navegação em duas linhas com flex-line-count. Disponível na versão HTML desta página.

### Layout masonry com flex-line-count

Dá para fazer um layout masonry com `flex-line-count`? A resposta curta é sim. Veja como.

Primeiro, um exemplo simples para testar a ideia; depois, um com conteúdo de verdade.

```css
.layout {
  display: flex;
  flex-direction: column;
  flex-wrap: balance;
  flex-line-count: 4;
  gap: 1.5rem;
}
```

> **Demonstração interativa:** masonry de teste com flex-line-count. Disponível na versão HTML desta página.

À primeira vista, está bom. Mas, ao redimensionar, o número de colunas continua o mesmo, e os itens ficam pequenos demais.

Como resolver? O plano é este:

- Transformar o layout em contêiner.
- Definir a largura do card.
- Dividir a largura do contêiner pela largura do card para obter o número de colunas.
- Usar a função `round()` para chegar ao inteiro mais próximo.

```css
.layout {
  --line-min: 190px;
  --line-count: clamp(1, round(down, 100cqw / var(--line-min)), 4);
  display: flex;
  flex-direction: column;
  flex-wrap: balance;
  flex-line-count: var(--line-count);
  gap: 1.5rem;
}
```

Veja a demonstração abaixo:

> **Demonstração interativa:** masonry de cards com o número de colunas calculado pela largura. Disponível na versão HTML desta página.

<figure>
  <video controls muted playsinline preload="metadata" width="1000" height="724">
    <source src="/images/blog/flex-wrap-balance/gravacao-masonry-flex-line-count.webm" type="video/webm" />
  </video>
  <figcaption>Gravação da demonstração acima no Chrome, para quem lê num navegador sem suporte a <code>flex-line-count</code>.</figcaption>
</figure>

Esta demonstração é só um experimento, e vale lembrar que a ordem de leitura não é a mesma da ordem visual (os números nos cards mostram a ordem no HTML). Ainda assim, ela leva o `flex-line-count` ao máximo do que ele consegue fazer, ou quase.

Para outra abordagem de masonry que está a caminho do CSS, veja <a href="/css/item-flow-sistema-layout-css/" target="_blank">Item Flow, o novo sistema de layout para CSS</a>.

## Conclusão

O `flex-wrap: balance` é um ótimo recurso novo, que vale usar cada vez mais. A surpresa foi o `flex-line-count`, bem mais útil do que parecia à primeira vista. Agora fica a curiosidade de ver o que a comunidade vai criar com os dois.

---

Fonte: [dpw - desenvolvimento para web](https://dpw.dev/) (pt-BR).
Guia para agentes: https://dpw.dev/llms.txt
Índice completo: https://dpw.dev/sitemap-index.xml
Feed: https://dpw.dev/rss.xml
