Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

flex-grow, flex-shrink e flex-basis: dividir o espaço

Como a sobra e a falta de espaço são repartidas entre os itens, o que flex: 1 realmente expande e por que flex-basis vence width numa linha flex.

Rodolfo Mori11 min de leitura

Num contêiner flex, o navegador primeiro calcula o tamanho base de cada item (flex-basis), depois soma tudo e vê se sobrou ou faltou espaço. Se sobrou, flex-grow reparte a sobra. Se faltou, flex-shrink reparte a falta. Só isso.

O que confunde é que as três propriedades quase nunca aparecem escritas. Elas vivem escondidas dentro do atalho flex, e cada palavra-chave (1, auto, initial, none) muda as três de uma vez.

Todos os exemplos desta lição são o painel da recepção de uma clínica veterinária, a Pata Firme: três cartões lado a lado — agenda, fila de espera e avisos do dia. Cada número colado aqui foi medido com getBoundingClientRect() no Chromium 151 headless, controlado por Playwright 1.62.1, num MacBook. Se você já sabe montar a linha e alinhar os itens dentro dela, esta é a lição seguinte a Flexbox no CSS.

html
<div class="painel">
  <section class="cartao agenda">Agenda</section>
  <section class="cartao fila">Fila de espera</section>
  <aside class="cartao avisos">Avisos</aside>
</div>

Duas regras entram antes de todos os exemplos, e é honesto declarar as duas porque elas mexem no número medido. A primeira é o border-box: com o content-box padrão, flex-basis: 180px num cartão com padding: 12px vira uma caixa de 204px, e não os 180px que a declaração promete. A segunda fixa a fonte, que é o que decide a largura de qualquer cartão medido pelo próprio conteúdo.

css
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; font-size: 16px; line-height: 1.4; }

As três propriedades que moram dentro de flex: 1

flex é um atalho para flex-grow flex-shrink flex-basis, nessa ordem. Escrever só um número altera as três. Este bloco declara seis variações e o script lê o valor computado de cada uma:

css
.c1 { flex: 1; }
.c2 { flex: auto; }
.c3 { flex: initial; }
.c4 { flex: none; }
.c5 { flex: 2 0 200px; }
/* .c6 não declara nada */
declaração | grow | shrink | basis flex: 1 | 1 | 1 | 0% flex: auto | 1 | 1 | auto flex: initial | 0 | 1 | auto flex: none | 0 | 0 | auto flex: 2 0 200px| 2 | 0 | 200px nada declarado | 0 | 1 | auto

Duas coisas para guardar dessa tabela. A primeira: flex: 1 não é o mesmo que flex-grow: 1, porque também zera a base. A segunda: um item flex já nasce encolhível — o padrão é 0 1 auto, com flex-shrink: 1 ligado sem você pedir.

1. flex-basis 2. sobra ou falta 3. grow ou shrink tamanho base de cada item item A item B sobra da linha largura do contêiner

flex-basis: o tamanho antes de qualquer distribuição

flex-basis é o ponto de partida do item no eixo principal. Ele aceita as mesmas unidades do CSS que width — pixel, rem, porcentagem, min-content, max-content — e mais duas palavras-chave próprias: auto, que devolve a decisão para o width declarado (ou para o tamanho do conteúdo, quando não há width), e content, que ignora o width e usa sempre o conteúdo.

Dentro de uma linha flex, quando os dois existem, flex-basis ganha de width. Dois cartões no mesmo painel, um com class="cartao so-width" e o outro com class="cartao com-basis":

css
.painel { display: flex; width: 900px; }
.cartao { flex-grow: 0; flex-shrink: 0; padding: 12px; }
.so-width  { width: 300px; }
.com-basis { width: 300px; flex-basis: 180px; }
.so-width → 300px .com-basis → 180px

O width: 300px do segundo cartão não sumiu; ele só perdeu a disputa. Isso é útil quando você quer um tamanho para o item fora do flex (numa impressão, num contêiner que deixa de ser flex no mobile) e outro dentro dele.

flex-grow reparte a sobra, e aceita fração

Com três cartões de base 120px num painel de 900px e gap: 20px, a conta da sobra é: 900 − (3 × 120) − (2 × 20) = 500px. É esse número que flex-grow divide.

css
.painel { display: flex; gap: 20px; width: 900px; }
.cartao { flex-basis: 120px; flex-grow: 1; padding: 12px; }
grow 1 / 1 / 1 → 286.66px, 286.67px, 286.67px

Cada um levou 500 ÷ 3 = 166,67px em cima dos 120px de base. O primeiro saiu com um centésimo a menos porque a soma tem que fechar exatamente os 860px da linha, e 166,67 não divide 500 sem sobra — o navegador acerta a conta no último subpixel em vez de deixar a linha estourar. Agora dando peso 3 ao primeiro cartão:

css
.agenda { flex-grow: 3; }
grow 3 / 1 / 1 → 420px, 220px, 220px

A soma dos fatores é 5, então a sobra é cortada em cinco fatias de 100px. A agenda leva três (300px) e vai a 420px; os outros levam uma cada e vão a 220px. Repare que a proporção final não é 3 para 1 — 420 dividido por 220 dá 1,9. flex-grow reparte a sobra, não a largura total.

E agora o detalhe que quase ninguém testa: fatores fracionários.

css
.agenda { flex-grow: 0.5; }
.fila   { flex-grow: 0.2; }
.avisos { flex-grow: 0.1; }
grow 0.5 / 0.2 / 0.1 → 370px, 220px, 170px

A soma é 0,8, menor que 1. Nesse caso a especificação manda distribuir apenas essa fração da sobra: 0,8 × 500px = 400px. Os 100px restantes ficam vazios no fim da linha. Some as larguras: 370 + 220 + 170 = 760, mais 40 de gap, dá 800px num painel de 900px. Esse espaço órfão é entregue ao justify-content, que decide onde ele aparece.

flex-shrink pondera pelo tamanho base — e não é o espelho do grow

Aqui mora o erro conceitual mais caro do Flexbox. Quase todo mundo assume que flex-shrink: 1 em dois itens tira a mesma quantidade de pixels de cada um. Não tira. O navegador multiplica o fator de shrink pelo tamanho base do item antes de dividir a falta.

Dois cartões, base 400px e 200px, num painel de 500px. Faltam 100px:

css
.painel { display: flex; width: 500px; }
.cartao { flex-shrink: 1; overflow: hidden; }
.fila   { flex-basis: 400px; }
.avisos { flex-basis: 200px; }
shrink 1 / 1 → 333.33px / 166.67px

Não deu 250 e 250. A conta real é: pesos 400 × 1 = 400 e 200 × 1 = 200, soma 600. A fila perde 100 × (400 ÷ 600) = 66,67px e chega a 333,33px. Os avisos perdem 100 × (200 ÷ 600) = 33,33px e chegam a 166,67px. Quem é maior encolhe mais, na proporção do próprio tamanho.

Aumentar o fator só empurra essa ponderação:

css
.fila { flex-shrink: 2; }
shrink 2 / 1 → 320px / 180px

Pesos 800 e 200, soma 1000: a fila perde 80px, os avisos perdem 20px. E o valor que resolve a maioria dos problemas de layout na prática é o zero, que congela um item:

css
.avisos { flex-shrink: 0; }
shrink 1 / 0 → 300px / 200px

Os avisos ficaram intactos em 200px e a fila absorveu os 100px que faltavam sozinha. É assim que se protege uma barra lateral, um avatar ou um botão que não pode espremer.

flex: 1, flex: auto, flex: initial e flex: none

Agora dá para ler as quatro palavras-chave com a tabela da primeira seção na mão. Quatro painéis de 600px, gap: 10px, cada um com o mesmo trio de cartões — e é o texto de cada cartão que define a base, então ele importa tanto quanto o CSS:

html
<div class="painel p1">
  <div class="cartao">Agenda</div>
  <div class="cartao">Fila de espera</div>
  <div class="cartao">Vacinas atrasadas</div>
</div>
css
.painel { display: flex; gap: 10px; width: 600px; }
.cartao { white-space: nowrap; }
.p1 .cartao { flex: 1; }       /* 1 1 0%   */
.p2 .cartao { flex: auto; }    /* 1 1 auto */
.p3 .cartao { flex: initial; } /* 0 1 auto */
.p4 .cartao { flex: none; }    /* 0 0 auto */
flex: 1 → 193.33px 193.34px 193.33px flex: auto → 152.81px 198.42px 228.77px flex: initial → 55.33px 100.92px 131.27px flex: none → 55.33px 100.92px 131.27px

Com flex: 1 os três ficam idênticos, porque a base é zero e a linha inteira (580px, já descontado o gap) é dividida em três partes iguais. Com flex: auto cada cartão começa no tamanho do próprio texto — 55,33, 100,92 e 131,27 — e só os 292,48px de sobra são divididos em três, dando 97,49px para cada um. É por isso que flex: auto mantém a diferença entre os itens e flex: 1 a apaga.

initial e none deram o mesmo resultado porque nada faltou. A diferença aparece quando o espaço acaba. Os mesmos cartões num painel de 240px — e repare que o white-space: nowrap sai de cena aqui, porque um texto que não quebra não tem como encolher:

css
.painel { display: flex; gap: 10px; width: 240px; }
.cartao { white-space: normal; }
flex: initial → 55.33px 71.58px 93.09px | soma com gap: 240.00px flex: none → 55.33px 100.92px 131.27px | soma com gap: 307.52px

flex: initial espremeu o que dava para espremer e fechou a linha nos 240px exatos. flex: none recusou, e a linha transbordou 67,52px para fora do painel. É a mesma escolha do dia a dia: none para o que não pode deformar, initial para o que pode.

Repare que o primeiro cartão não se mexeu: continuou em 55,33px nos dois. Ele já estava no mínimo, porque “Agenda” é uma palavra só e não tem onde quebrar. Os 67,52px que faltavam saíram todos dos outros dois, na ponderação da seção anterior. Esse piso por palavra é o começo da história da próxima seção.

min-width: auto, o piso invisível que trava o encolhimento

Este é o bug que mais consome tempo. Item flex tem min-width computado como auto, e auto ali significa “o tamanho mínimo do conteúdo”. Enquanto o conteúdo for texto que não quebra, o item se recusa a encolher — mesmo com flex: 1 e flex-shrink: 1 ligados.

A linha da fila de espera tem o e-mail do tutor e um botão:

html
<div class="linha">
  <div class="tutor">
    <span class="email">carla.mendonca@clinicapatafirme.com.br</span>
  </div>
  <button class="chamar">Chamar para a sala 2</button>
</div>
css
.linha  { display: flex; gap: 8px; width: 320px; }
.tutor  { flex: 1; }
.email  { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chamar { flex: none; }
.tutor ..... 300.86px .chamar .... 144.19px soma + gap . 453.05px (a linha tem 320px)

O .tutor deveria ter encolhido até sobrar 168px. Em vez disso parou em 300,86px — exatamente a largura do e-mail sem quebra de linha — e a linha estourou 133px. O text-overflow: ellipsis nunca chegou a agir, porque o item nunca ficou menor que o texto.

A correção é declarar o piso na mão:

css
.tutor { flex: 1; min-width: 0; }
nada → .tutor 300.86px | soma com gap 453.05px min-width: 0 → .tutor 167.81px | soma com gap 320.00px overflow: hidden→ .tutor 167.81px | soma com gap 320.00px

overflow: hidden no item resolve igual, e pelo mesmo motivo: min-width: auto só vira o tamanho do conteúdo quando o overflow do item é visible. Em coluna, a propriedade a zerar é min-height.

Duas colunas 2 para 1: a conta linha por linha

O caso mais comum de painel: conteúdo principal com o dobro da largura da barra lateral. Painel de 900px, gap: 24px. O texto de cada bloco vai fazer diferença daqui a pouco, então ele fica à vista:

html
<div class="painel">
  <section class="consultas">Consultas de hoje</section>
  <aside class="avisos">Avisos da recepção da clínica veterinária Pata Firme</aside>
</div>
css
.painel    { display: flex; gap: 24px; width: 900px; }
.consultas { flex-grow: 2; flex-basis: 0; }
.avisos    { flex-grow: 1; flex-basis: 0; }
flex-basis: 0 → .consultas 584.00px | .avisos 292.00px | proporção 2.00 : 1

Passo a passo: base zero nos dois, então o espaço disponível é 900 − 24 = 876px. A soma dos fatores é 3, cada fatia vale 292px. As consultas levam duas fatias (584px) e os avisos, uma (292px). A proporção sai exata porque o gap foi descontado antes da divisão.

Troque a base para auto e a mesma declaração de grow deixa de funcionar:

css
.consultas { flex-grow: 2; flex-basis: auto; }
.avisos    { flex-grow: 1; flex-basis: auto; }
flex-basis: auto → .consultas 377.64px | .avisos 498.36px | proporção 0.76 : 1

A barra lateral ficou maior que o conteúdo principal, apesar de ter metade do fator de crescimento. O motivo é que o texto dos avisos é mais longo, então a base auto dela já nasceu maior, e o grow só repartiu a sobra em cima disso. Proporção fixa exige base fixa. É exatamente por isso que flex: 1 expande para 1 1 0% em vez de 1 1 auto.

Quando parar de insistir no Flexbox e usar Grid

Duas situações em que a divisão de espaço do Flexbox custa mais do que rende.

A primeira é porcentagem com gap. Três cartões de um terço cada, num painel de 900px com gap: 24px:

css
.painel { display: flex; gap: 24px; width: 900px; }
.p .cartao { flex: 0 0 33.3333%; }
.z .cartao { flex: 1 1 0; }
flex: 0 0 33.3333% → 299.98px 299.98px 299.98px | soma com gap: 947.94px flex: 1 1 0 → 284.00px 284.00px 284.00px | soma com gap: 900.00px

A porcentagem é calculada sobre a largura do contêiner e ignora o gap, então a linha estoura em 47,94px. flex: 1 1 0 acerta porque divide o que sobrou depois do gap. Guarde a regra: em linha flex com gap, base zero e grow, nunca porcentagem.

A segunda é a última fileira de uma grade que quebra. Cinco cartões de pet com flex: 1 1 200px e flex-wrap: wrap, contra o equivalente em CSS Grid:

css
.f { display: flex; flex-wrap: wrap; gap: 24px; width: 900px; }
.f .pet { flex: 1 1 200px; }

.g { display: grid; gap: 24px; width: 900px;
     grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
flex-wrap → 207px 207px 207px 207px 900px grid → 207px 207px 207px 207px 207px

No Flexbox, o quinto cartão ficou sozinho na segunda fileira e cresceu para os 900px inteiros, porque cada linha reparte a própria sobra sem saber da linha de cima. O Grid manteve as colunas. Sempre que o alinhamento precisar valer entre fileiras, o trabalho é de Grid — o comparativo entre Flexbox e Grid detalha os outros critérios.

O que treinar agora

Pegue um componente que já esteja no seu projeto e faça três coisas: troque todo flex: 1 de item com conteúdo variável por flex: 1 1 0 explícito, ponha flex-shrink: 0 no que não pode espremer e acrescente min-width: 0 em todo item que contém texto longo. Depois meça no DevTools, item por item, e confira se o número bate com a conta que você fez no papel.

A próxima lição da trilha de CSS sai do eixo único e entra no Grid, onde linhas e colunas são planejadas juntas. Se o seu problema agora é tamanho de caixa e não distribuição de espaço, volte um passo e revise o box model — quase toda conta de flex-basis que não fecha tem padding ou borda no meio.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • css
  • flexbox
  • flex-grow
  • flex-basis
  • flex-shrink

Perguntas frequentes

Qual a diferença entre flex-basis e width?
width é o tamanho do elemento em qualquer contexto. flex-basis é o tamanho inicial dele só no eixo principal de uma linha flex, e tem prioridade sobre width ali dentro. Fora de um contêiner flex, flex-basis é ignorado.
Por que flex: 1 e flex: auto dão resultados diferentes?
flex: 1 expande para 1 1 0%, então todo item parte do zero e a linha inteira é repartida pelos fatores de grow. flex: auto expande para 1 1 auto, ou seja, cada item já começa do tamanho do próprio conteúdo e só a sobra é dividida.
Posso usar flex-grow com número decimal?
Pode. Se a soma dos fatores da linha for menor que 1, só essa fração da sobra é distribuída e o resto do espaço fica vazio. Com soma 0.8, por exemplo, 20% da sobra não vai para ninguém.
Por que meu item com flex: 1 não encolhe além de um certo ponto?
Porque item flex tem min-width computado como auto, e isso vira o tamanho mínimo do conteúdo. Declare min-width 0 (ou overflow diferente de visible) no item para liberar o encolhimento.
flex-shrink 2 encolhe o dobro de flex-shrink 1?
Não necessariamente. O fator de shrink é multiplicado pelo tamanho base do item antes da divisão, então um item pequeno com shrink 2 pode perder menos pixels que um item grande com shrink 1.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Chromium 151.0.7922.34 (headless, Playwright 1.62.1), e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. CSS Flexible Box Layout Module Level 1 — Resolving Flexible Lengths — w3.org
  2. MDN — Controlling ratios of flex items along the main axis — developer.mozilla.org
  3. MDN — flex-basis — developer.mozilla.org

Continue por aqui