justify-content e align-items: alinhar no Flexbox
Como distribuir e alinhar itens nos dois eixos, a diferença entre align-items e align-content, e o gap que aposentou a margem entre os filhos.
justify-content reparte o espaço que sobrou no eixo principal do contêiner
flex. align-items posiciona cada item no eixo transversal. As duas moram no
pai, nunca no filho, e as duas trabalham sobre a sobra: quando não sobra
nada no eixo, trocar o valor não muda um pixel.
Toda medida desta lição saiu do app de entrega de uma pizzaria, a Forno Real:
uma barra de navegação de 600 pixels, uma vitrine de pizzas e uma faixa de
promoção. As posições foram lidas com getBoundingClientRect() no Chromium
151.0.7922.34, dirigido pelo Playwright 1.62.1 — nenhum número aqui foi
estimado. Se os termos “eixo principal” e “flex container” ainda soam novos,
comece por Flexbox CSS: alinhe e distribua elementos.
justify-content reparte a sobra, não os itens
A barra do topo tem três links de 80 pixels dentro de 600. O HTML é uma lista simples de âncoras:
<nav class="barra">
<a class="link">Cardápio</a>
<a class="link">Pedidos</a>
<a class="link">Sacola</a>
</nav>.barra {
display: flex;
width: 600px;
}
.link {
flex: 0 0 80px;
height: 40px;
}Para medir, li a posição de cada filho em relação à borda esquerda do contêiner
e troquei o valor de justify-content três vezes:
const barra = document.querySelector('.barra');
for (const valor of ['flex-start', 'center', 'flex-end']) {
barra.style.justifyContent = valor;
const base = barra.getBoundingClientRect();
const posicoes = [...barra.children].map((link) => {
const caixa = link.getBoundingClientRect();
return `${link.textContent} x=${caixa.x - base.x}`;
});
console.log(valor.padEnd(11), ':', posicoes.join(' '));
}Repare que os três links continuam com 80 pixels em todos os casos. O que muda
é onde a sobra de 360 pixels foi colocada: toda depois (flex-start),
metade de cada lado (center) ou toda antes (flex-end). Guardar essa frase
resolve metade das dúvidas de Flexbox: justify-content move a sobra, não os
itens.
Sem sobra, ele não faz nada. Se um dos links tivesse flex: 1 e engolisse os
360 pixels livres, trocar flex-start por center não mudaria um pixel. Quem
decide o tamanho dos itens é outro trio de propriedades, o de
flex-grow, flex-shrink e flex-basis.
space-between, space-around e space-evenly: as folgas em números
Os três valores space-* são o ponto em que a explicação escrita costuma
falhar, porque “espaço ao redor” e “espaço uniforme” parecem sinônimos. Não são.
Medi as quatro folgas de cada configuração — a folga antes do primeiro link, as
duas entre os links e a folga depois do último:
const barra = document.querySelector('.barra');
for (const valor of ['space-between', 'space-around', 'space-evenly']) {
barra.style.justifyContent = valor;
const base = barra.getBoundingClientRect();
const itens = [...barra.children].map((link) => {
const caixa = link.getBoundingClientRect();
return { x: caixa.x - base.x, largura: caixa.width };
});
const folgas = [itens[0].x];
for (let i = 1; i < itens.length; i++) {
folgas.push(itens[i].x - (itens[i - 1].x + itens[i - 1].largura));
}
folgas.push(barra.clientWidth - (itens.at(-1).x + itens.at(-1).largura));
console.log(valor.padEnd(14), 'x:', itens.map((i) => i.x).join(' | '), ' folgas:', folgas.join(' / '));
}Agora a diferença tem número:
| valor | como reparte os 360 pixels | folgas resultantes |
|---|---|---|
space-between |
só entre os itens, 2 intervalos de 180 | 0 / 180 / 180 / 0 |
space-around |
120 para cada item, metade de cada lado dele | 60 / 120 / 120 / 60 |
space-evenly |
4 intervalos idênticos de 90 | 90 / 90 / 90 / 90 |
Em space-around, cada item leva 120 pixels e usa 60 de cada lado. Como os 60
do primeiro link encostam nos 60 do segundo, o miolo fica com 120 e as pontas
com 60 — a metade. É por isso que space-around sempre parece “torto”: as
bordas ficam mais apertadas do que os vãos internos. space-evenly corrige isso
dividindo por quatro, e por isso é o valor certo quando você quer um menu que
pareça respirar igual em toda a barra.
align-items trabalha no eixo transversal
Na vitrine, cada pizza é um cartão de 160 pixels dentro de uma faixa de 160 de altura. Aqui o eixo interessante é o vertical:
<section class="vitrine">
<article class="pizza">Margherita</article>
<article class="pizza">Calabresa</article>
<article class="pizza">Portuguesa</article>
</section>.vitrine {
display: flex;
width: 600px;
height: 160px;
gap: 20px;
}
.pizza {
flex: 0 0 160px;
padding: 12px;
line-height: 24px;
}O cartão tem 48 pixels de altura natural: 24 de linha mais 12 de padding em
cima e 12 embaixo. Sobram 112 no eixo transversal. center deixa 56 acima,
flex-end deixa os 112 acima. Mesma lógica de repartir sobra, outro eixo.
A linha stretch é a que mais chama atenção: a altura pulou de 48 para 160. Ela
merece uma seção só.
stretch: o valor padrão que ninguém escreve e todo mundo usa
Aqui mora uma pegadinha de DevTools. Se você abrir um contêiner flex que não
declara align-items e pedir o valor calculado, não vem stretch:
const vitrine = document.querySelector('.vitrine');
console.log(getComputedStyle(vitrine).alignItems);O valor inicial de align-items é normal — e normal, dentro de um contêiner
flex, se comporta exatamente como stretch. Por isso o painel do navegador
mostra uma palavra que você nunca escreveu e que não aparece em tutorial nenhum.
E stretch não posiciona: ele estica o item até preencher o eixo
transversal, desde que o item não tenha altura própria. É o motivo de os cartões
de uma listagem saírem todos do mesmo tamanho sem você pedir.
Para ver isso valendo dinheiro, tirei a altura fixa da vitrine e coloquei descrições de comprimentos diferentes:
<section class="vitrine">
<article class="pizza">Margherita<p>Molho, muçarela e manjericão fresco</p></article>
<article class="pizza">Calabresa<p>Calabresa e cebola</p></article>
<article class="pizza">Portuguesa<p>Presunto, ovo, cebola, azeitona e ervilha</p></article>
</section>/* o padding precisa caber dentro dos 180px, senão o texto quebra em
outro ponto e as alturas abaixo mudam */
* { box-sizing: border-box; }
.vitrine {
display: flex;
width: 600px;
gap: 20px;
/* sem height: quem manda na altura é o cartão mais alto */
}
.pizza {
flex: 0 0 180px;
padding: 12px;
line-height: 24px;
}
.pizza p {
margin: 8px 0 0;
}Com o padrão stretch, os três cartões ficam com 128 pixels, a altura do mais
alto. Basta escrever align-items: flex-start para o desenho desandar: 104, 80
e 128, com os fundos desalinhados. Quando alguém diz “meus cards ficaram
irregulares depois que eu mexi no alinhamento”, quase sempre foi isso.
align-content só existe quando há mais de uma linha
align-items alinha os itens dentro da linha. align-content alinha as
linhas dentro do contêiner. A segunda propriedade só tem trabalho a fazer
quando o flex-wrap criou mais de uma linha e ainda sobrou altura.
Na grade de pizzas, quatro cartões de 280 pixels em 600 formam duas linhas:
.grade {
display: flex;
flex-wrap: wrap;
width: 600px;
height: 300px;
gap: 20px;
}
.pizza {
flex: 0 0 280px;
height: 60px;
}As duas linhas ocupam 140 pixels somados (60 + 20 de gap + 60) e sobram 160.
Com flex-start, a segunda linha começa em 80 — logo depois da primeira. Com
space-between, ela vai para 240, encostada no fundo. O stretch do topo é
sutil: as duas linhas ficaram com 140 de altura cada, mas os cartões
continuaram com 60, porque a altura deles está fixa no CSS. A linha esticou; o
item, não.
Agora o teste que separa as duas propriedades. Mesmo CSS, trocando só
flex-wrap: wrap por nowrap:
.grade {
flex-wrap: nowrap;
align-content: center;
}Com uma linha só, align-content: center não moveu absolutamente nada.
align-items: center moveu os quatro para y=120. Se você escreveu
align-content e o layout ficou parado, a pergunta certa é: existe flex-wrap
neste contêiner, e ele realmente quebrou em duas linhas?
align-self: a exceção para um item só
align-self está no filho e sobrescreve o align-items do pai naquele item.
No cabeçalho da pizzaria, o selo de horário desce para a base enquanto marca e
endereço ficam centralizados:
.topo {
display: flex;
align-items: center;
width: 600px;
height: 120px;
gap: 20px;
}
.topo > * {
flex: 0 0 160px;
height: 40px;
}
.selo {
align-self: flex-end;
}Dois itens em y=40 (centralizados nos 120 de altura) e o selo em y=80, encostado
embaixo. Trocando o selo para align-self: stretch e devolvendo a altura
automática, ele passa a ocupar a faixa inteira:
.selo {
align-self: stretch;
height: auto;
}O par tem uma assimetria que confunde: existe align-self, mas não existe
justify-self no Flexbox. Testei um item com justify-self: end e as
posições ficaram idênticas. Quem faz esse papel é a margem automática, logo
abaixo.
gap, row-gap e column-gap: a distância que não é sobra
gap é distância fixa entre itens. justify-content é sobra repartida. As duas
convivem, e a ordem é clara: o navegador reserva os gap primeiro e só depois
distribui o que restou. Voltando à barra de 600 pixels, com os mesmos três links
centralizados:
.barra {
display: flex;
justify-content: center;
gap: 24px; /* medi antes com 0 e depois com 24 */
width: 600px;
}Com justify-content: center e gap: 24px, o conjunto passou a ocupar 288
pixels em vez de 240, e o bloco inteiro se reposicionou para continuar
centralizado. O item do meio nem se mexeu: continua em 260.
O gap também resolve um defeito antigo. Antes dele, a distância entre itens
era feita com margin-right — e a margem do último item continua lá, comendo
espaço na ponta:
/* o jeito antigo, e o problema dele */
.barra { display: flex; justify-content: space-between; width: 600px; }
.link { margin-right: 24px; }Com margem, o último link termina em 576 e sobram 24 pixels mortos à direita —
o que gerava aquele :last-child { margin-right: 0 } em todo projeto. Com
gap, o último termina exatamente em 600. A margem entre filhos foi aposentada.
Quando o contêiner quebra linha, dá para separar os dois sentidos: column-gap
vale entre colunas e row-gap entre linhas.
.grade {
display: flex;
flex-wrap: wrap;
width: 600px;
row-gap: 40px;
column-gap: 8px;
}
.pizza {
flex: 0 0 280px;
height: 60px;
}O segundo cartão começa em 288 (280 + 8 de coluna) e a segunda linha em 100
(60 + 40 de linha). O atalho gap: 40px 8px escreve as duas de uma vez, na
ordem linha e coluna — a mesma ordem de margin: 40px 8px.
margin: auto come toda a sobra de uma vez
Um cabeçalho de app quase sempre pede “logo e links à esquerda, sacola à
direita”. space-between não resolve: ele espalha os links do meio junto.
<header class="cabecalho">
<span class="marca">Forno Real</span>
<a class="link">Cardápio</a>
<a class="link">Pedidos</a>
<a class="sacola">Sacola</a>
</header>.cabecalho {
display: flex;
align-items: center;
gap: 16px;
width: 600px;
height: 64px;
}
.cabecalho > * {
flex: 0 0 100px;
height: 32px;
}
.sacola {
margin-left: auto;
}A sacola fica em x = 500 nas duas versões — encostada na direita, já que ela
mede 100 pixels dentro de 600. O que muda é o miolo. Com margin-left: auto,
marca e links ficam agrupados em 0, 116 e 232, com os 16 pixels de gap entre
eles. Com space-between, eles se espalham para 166,67 e 333,33 e o agrupamento
se perde. A margem automática engole toda a sobra antes daquele item; é o
justify-self que o Flexbox não tem.
Com flex-direction: column, o par inteiro troca de lugar
Nada do que você aprendeu muda de nome quando o eixo vira coluna — mas muda de sentido. Na barra lateral do painel de pedidos:
.lateral {
display: flex;
flex-direction: column;
width: 300px;
height: 400px;
}
.atalho {
flex: 0 0 40px;
width: 120px;
}justify-content: space-between agora espalhou os atalhos na vertical
(y indo de 0 a 360) e align-items passou a mexer na horizontal (x saltando
de 0 para 90 e depois 180). É a mesma propriedade, o mesmo valor, e o resultado
girou 90 graus.
Daí a insistência em falar “eixo principal” e “eixo transversal” em vez de
“horizontal” e “vertical”. Quem decora justify-content = horizontal acerta
enquanto o layout é row e erra no primeiro menu empilhado no celular.
O erro mais comum: a regra certa no elemento errado
Este é o defeito que mais chega em dúvida de aluno, e ele tem duas variantes.
Variante 1: o alinhamento está no filho
.barra {
display: flex;
width: 600px;
height: 120px;
}
.link {
flex: 0 0 80px;
height: 40px;
justify-content: center;
align-items: center;
}Antes de culpar o navegador, perguntei a ele o que estava valendo no filho:
const s = getComputedStyle(document.querySelector('.link'));
console.log(JSON.stringify({
display: s.display,
justifyContent: s.justifyContent,
alignItems: s.alignItems,
}));Nada se moveu — e o getComputedStyle mostra o porquê, sem aliviar a
frustração: as duas propriedades estão aplicadas, com o valor center
certinho. O problema é a linha ao lado: display: block. Alinhamento de caixa
só age sobre os filhos de um contêiner flex ou grid, e .link não é nenhum dos
dois. O DevTools não risca a declaração, porque ela é válida; ela só não tem
sobre quem agir.
A leitura prática: quando justify-content “não funciona”, olhe se você
escreveu a regra no elemento que tem display: flex. Na dúvida, o distintivo
flex que o DevTools desenha ao lado do seletor é o sinal visual mais rápido.
Variante 2: o contêiner não tem altura para centralizar
A faixa de promoção tem uma foto de 96 pixels e uma frase de uma linha:
<div class="faixa">
<img class="foto" src="/img/frete.png" alt="">
<p class="frase">Entrega grátis acima de R$ 60</p>
</div>.faixa {
display: flex;
align-items: center;
gap: 16px;
width: 600px;
}
.foto { width: 96px; height: 96px; }
.frase { margin: 0; line-height: 24px; }Aqui o align-items: center funcionou — só que num contêiner de 96 pixels, que
é exatamente a altura da foto. A imagem ficou em y=0 e a frase em y=36,
centralizada em relação à foto e a mais nada. A pessoa esperava a faixa de
promoção alta, com o conteúdo no meio.
A correção não é mexer no alinhamento, é dar altura ao pai. Com
min-height: 200px, a faixa passa a 200, a foto vai para 52 e a frase para 88 —
as duas centralizadas de verdade:
.faixa {
min-height: 200px;
}O que treinar agora
Abra o DevTools numa página qualquer, escolha um menu horizontal e mude
justify-content entre os seis valores desta lição lendo o x de cada item no
painel de layout. Depois refaça o mesmo menu com flex-direction: column e
confirme que os papéis trocaram. Cinco minutos disso valem mais que reler a
tabela.
O passo seguinte é decidir quem cresce e quem encolhe quando a tela muda, que é
o assunto de
flex-grow, flex-shrink e flex-basis. Se o
seu problema é só pôr uma caixa no meio da tela, o atalho está em
como centralizar uma div. E quando o layout
pedir linhas e colunas planejadas juntas, o Flexbox entrega o bastão para o
CSS Grid — que trabalha com os mesmos justify-content e
align-items, e ainda ganha o justify-items que falta aqui.
O mapa completo da trilha, com a ordem sugerida de estudo, está no guia de CSS e no índice da trilha de CSS.
Prefere aprender em vídeo?
Tem uma aula sobre este assunto no nosso canal.
Perguntas frequentes
Meu justify-content parou de funcionar depois que usei margin auto. Por quê?
O que space-between faz quando existe um item só?
Por que justify-content center corta o começo do conteúdo?
Posso usar place-content e place-items no Flexbox?
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 (Playwright 1.62.1), e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Aligning items in a flex container — developer.mozilla.org
- MDN — justify-content — developer.mozilla.org
- CSS Box Alignment Module Level 3 — W3C — w3.org



