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.
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.
<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.
* { 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:
.c1 { flex: 1; }
.c2 { flex: auto; }
.c3 { flex: initial; }
.c4 { flex: none; }
.c5 { flex: 2 0 200px; }
/* .c6 não declara nada */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.
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":
.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; }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.
.painel { display: flex; gap: 20px; width: 900px; }
.cartao { flex-basis: 120px; flex-grow: 1; padding: 12px; }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:
.agenda { flex-grow: 3; }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.
.agenda { flex-grow: 0.5; }
.fila { flex-grow: 0.2; }
.avisos { flex-grow: 0.1; }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:
.painel { display: flex; width: 500px; }
.cartao { flex-shrink: 1; overflow: hidden; }
.fila { flex-basis: 400px; }
.avisos { flex-basis: 200px; }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:
.fila { flex-shrink: 2; }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:
.avisos { flex-shrink: 0; }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:
<div class="painel p1">
<div class="cartao">Agenda</div>
<div class="cartao">Fila de espera</div>
<div class="cartao">Vacinas atrasadas</div>
</div>.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 */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:
.painel { display: flex; gap: 10px; width: 240px; }
.cartao { white-space: normal; }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:
<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>.linha { display: flex; gap: 8px; width: 320px; }
.tutor { flex: 1; }
.email { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chamar { flex: none; }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:
.tutor { flex: 1; min-width: 0; }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:
<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>.painel { display: flex; gap: 24px; width: 900px; }
.consultas { flex-grow: 2; flex-basis: 0; }
.avisos { flex-grow: 1; flex-basis: 0; }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:
.consultas { flex-grow: 2; flex-basis: auto; }
.avisos { flex-grow: 1; flex-basis: auto; }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:
.painel { display: flex; gap: 24px; width: 900px; }
.p .cartao { flex: 0 0 33.3333%; }
.z .cartao { flex: 1 1 0; }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:
.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)); }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.
Perguntas frequentes
Qual a diferença entre flex-basis e width?
Por que flex: 1 e flex: auto dão resultados diferentes?
Posso usar flex-grow com número decimal?
Por que meu item com flex: 1 não encolhe além de um certo ponto?
flex-shrink 2 encolhe o dobro de flex-shrink 1?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- CSS Flexible Box Layout Module Level 1 — Resolving Flexible Lengths — w3.org
- MDN — Controlling ratios of flex items along the main axis — developer.mozilla.org
- MDN — flex-basis — developer.mozilla.org



