Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

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

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.

Rodolfo Mori12 min de leitura

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.

flex-direction: row flex-direction: column principal transversal transversal principal justify-content atua no eixo principal; align-items, no transversal

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:

html
<nav class="barra">
  <a class="link">Cardápio</a>
  <a class="link">Pedidos</a>
  <a class="link">Sacola</a>
</nav>
css
.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:

js
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('  '));
}
flex-start : Cardápio x=0 Pedidos x=80 Sacola x=160 center : Cardápio x=180 Pedidos x=260 Sacola x=340 flex-end : Cardápio x=360 Pedidos x=440 Sacola x=520 largura da barra: 600 | soma dos itens: 240 | sobra: 360

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:

js
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(' / '));
}
space-between x: 0 | 260 | 520 folgas: 0 / 180 / 180 / 0 space-around x: 60 | 260 | 460 folgas: 60 / 120 / 120 / 60 space-evenly x: 90 | 260 | 430 folgas: 90 / 90 / 90 / 90

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:

html
<section class="vitrine">
  <article class="pizza">Margherita</article>
  <article class="pizza">Calabresa</article>
  <article class="pizza">Portuguesa</article>
</section>
css
.vitrine {
  display: flex;
  width: 600px;
  height: 160px;
  gap: 20px;
}

.pizza {
  flex: 0 0 160px;
  padding: 12px;
  line-height: 24px;
}
stretch : y=0 altura=160 y=0 altura=160 y=0 altura=160 flex-start : y=0 altura=48 y=0 altura=48 y=0 altura=48 center : y=56 altura=48 y=56 altura=48 y=56 altura=48 flex-end : y=112 altura=48 y=112 altura=48 y=112 altura=48

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:

js
const vitrine = document.querySelector('.vitrine');
console.log(getComputedStyle(vitrine).alignItems);
normal

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:

html
<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>
css
/* 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;
}
align-items: stretch alturas: 128 | 128 | 128 | vitrine: 128 align-items: flex-start alturas: 104 | 80 | 128 | vitrine: 128

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:

css
.grade {
  display: flex;
  flex-wrap: wrap;
  width: 600px;
  height: 300px;
  gap: 20px;
}

.pizza {
  flex: 0 0 280px;
  height: 60px;
}
align-content: stretch y: 0 | 0 | 160 | 160 | alturas: 60 | 60 | 60 | 60 align-content: flex-start y: 0 | 0 | 80 | 80 | alturas: 60 | 60 | 60 | 60 align-content: center y: 80 | 80 | 160 | 160 | alturas: 60 | 60 | 60 | 60 align-content: space-between y: 0 | 0 | 240 | 240 | alturas: 60 | 60 | 60 | 60 align-content: space-around y: 40 | 40 | 200 | 200 | alturas: 60 | 60 | 60 | 60

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:

css
.grade {
  flex-wrap: nowrap;
  align-content: center;
}
nowrap + align-content: center: y=0 | y=0 | y=0 | y=0 nowrap + align-items: center : y=120 | y=120 | y=120 | y=120

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:

css
.topo {
  display: flex;
  align-items: center;
  width: 600px;
  height: 120px;
  gap: 20px;
}

.topo > * {
  flex: 0 0 160px;
  height: 40px;
}

.selo {
  align-self: flex-end;
}
Forno Real y = 40 | altura = 40 Rua das Oliveiras, 120 y = 40 | altura = 40 Aberto até 23h y = 80 | altura = 40

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:

css
.selo {
  align-self: stretch;
  height: auto;
}
Forno Real y = 40 | altura = 40 Rua das Oliveiras, 120 y = 40 | altura = 40 Aberto até 23h y = 0 | altura = 120

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:

css
.barra {
  display: flex;
  justify-content: center;
  gap: 24px; /* medi antes com 0 e depois com 24 */
  width: 600px;
}
gap: 0 x: 180 | 260 | 340 ocupado: 240 de 600 gap: 24px x: 156 | 260 | 364 ocupado: 288 de 600

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:

css
/* o jeito antigo, e o problema dele */
.barra { display: flex; justify-content: space-between; width: 600px; }
.link  { margin-right: 24px; }
margin-right: 24px + space-between x: 0 | 248 | 496 ocupado: 576 de 600 gap: 24px + space-between x: 0 | 260 | 520 ocupado: 600 de 600

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.

css
.grade {
  display: flex;
  flex-wrap: wrap;
  width: 600px;
  row-gap: 40px;
  column-gap: 8px;
}

.pizza {
  flex: 0 0 280px;
  height: 60px;
}
Margherita x=0 y=0 Calabresa x=288 y=0 Portuguesa x=0 y=100 Frango x=288 y=100 altura total da grade: 160

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.

html
<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>
css
.cabecalho {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 600px;
  height: 64px;
}

.cabecalho > * {
  flex: 0 0 100px;
  height: 32px;
}

.sacola {
  margin-left: auto;
}
Forno Real x = 0 Cardápio x = 116 Pedidos x = 232 Sacola x = 500 --- o mesmo cabecalho com justify-content: space-between --- Forno Real x = 0 Cardápio x = 166.67 Pedidos x = 333.33 Sacola x = 500

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:

css
.lateral {
  display: flex;
  flex-direction: column;
  width: 300px;
  height: 400px;
}

.atalho {
  flex: 0 0 40px;
  width: 120px;
}
padrão (column) (x=0, y=0) (x=0, y=40) (x=0, y=80) justify-content: space-between (x=0, y=0) (x=0, y=180) (x=0, y=360) + align-items: center (x=90, y=0) (x=90, y=180) (x=90, y=360) + align-items: flex-end (x=180, y=0) (x=180, y=180) (x=180, y=360)

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

css
.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:

js
const s = getComputedStyle(document.querySelector('.link'));

console.log(JSON.stringify({
  display: s.display,
  justifyContent: s.justifyContent,
  alignItems: s.alignItems,
}));
regra escrita no FILHO : x=0 y=0 x=80 y=0 x=160 y=0 computed style do filho: {"display":"block","justifyContent":"center","alignItems":"center"} regra escrita no PAI : x=180 y=40 x=260 y=40 x=340 y=40

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:

html
<div class="faixa">
  <img class="foto" src="/img/frete.png" alt="">
  <p class="frase">Entrega grátis acima de R$ 60</p>
</div>
css
.faixa {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 600px;
}

.foto  { width: 96px; height: 96px; }
.frase { margin: 0; line-height: 24px; }
align-items: center, sem altura faixa = 96 y=0 (h=96) y=36 (h=24) + min-height: 200px faixa = 200 y=52 (h=96) y=88 (h=24)

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:

css
.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.

Ver todos os vídeos do canal
  • css
  • flexbox
  • justify-content
  • align-items
  • gap

Perguntas frequentes

Meu justify-content parou de funcionar depois que usei margin auto. Por quê?
Porque a margem automática absorve a sobra antes de o alinhamento entrar em cena, e sem sobra o justify-content não tem o que repartir. Medi a mesma barra com flex-start, center, space-between e space-evenly tendo um item com margin-left auto: as quatro deram exatamente as mesmas posições.
O que space-between faz quando existe um item só?
Ele encosta esse item no começo, exatamente como flex-start. Já space-around centraliza o item sozinho, porque continua reservando meia folga de cada lado. É uma diferença que só aparece em lista dinâmica.
Por que justify-content center corta o começo do conteúdo?
Quando os itens são maiores que o contêiner, center distribui a sobra negativa nos dois lados e joga o começo para fora, com x negativo. A rolagem não alcança essa parte. O valor safe center evita o problema.
Posso usar place-content e place-items no Flexbox?
place-content funciona: ele é o atalho de align-content mais justify-content. place-items é atalho de align-items mais justify-items, e como justify-items é ignorado no Flexbox, só metade dele tem efeito.

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 (Playwright 1.62.1), e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Aligning items in a flex container — developer.mozilla.org
  2. MDN — justify-content — developer.mozilla.org
  3. CSS Box Alignment Module Level 3 — W3C — w3.org

Continue por aqui