Como centralizar uma div no CSS: 6 formas testadas
Centralizar na horizontal, na vertical e nos dois eixos com margin auto, Flexbox, Grid, position e transform — e qual escolher em cada situação.
Centralizar uma div é responder duas perguntas: em qual eixo e quem manda
no espaço. Só na horizontal, margin-inline: auto com uma largura definida
resolve. Nos dois eixos, display: grid; place-items: center no elemento pai
resolve em uma linha. As outras quatro formas existem porque cada uma sobrevive
a um problema diferente.
Todos os exemplos aqui são do painel de uma clínica veterinária, a Pata Amiga.
O .palco é a área da tela; o .aviso é o cartão “Consulta confirmada” que
precisa ficar no meio.
<section class="palco">
<div class="aviso">Consulta confirmada</div>
</section>Cada medida desta lição saiu de um Chromium 151 controlado por Playwright, com este reset em todas as páginas de teste:
* { box-sizing: border-box }
body { margin: 0; font: 16px/1.4 system-ui, sans-serif }A janela é de 900 × 640, menos nas comparações lado a lado, que usam 1000 × 700
para caber um palco de 901px. Os números são o getBoundingClientRect de
verdade, não estimativa — por isso aparecem casas decimais que nenhum tutorial
mostra.
Só na horizontal: margin-inline: auto e o width obrigatório
A margem automática funciona assim: o navegador calcula quanto espaço sobrou na linha e divide igualmente entre os dois lados. Se não sobrou nada, não há o que dividir.
.pagina { width: 900px; }
.cartao { width: 320px; margin-inline: auto; padding: 16px; }Sobraram 900 − 320 = 580 pixels, e cada lado ficou com 290. Agora tire a largura e rode de novo:
.pagina { width: 900px; }
.cartao { margin-inline: auto; padding: 16px; }A div é um elemento de bloco: sem width, ela ocupa a linha inteira do pai. Com
zero de sobra, a margem automática vira zero dos dois lados. Não é que o CSS
ignorou o auto — é que ele dividiu nada por dois.
Texto centralizado não é caixa centralizada
Essa é a confusão que mais atrasa quem está começando. text-align: center
centraliza o conteúdo em linha dentro da caixa, e não move a caixa.
<header class="topo">
<div class="selo"><strong class="marca">Pata Amiga</strong></div>
</header>.topo { width: 900px; text-align: center; }
.selo { border: 2px solid #333; padding: 12px; }Repare que os dois centros dão 450. O texto está centralizado mesmo. Mas a
borda de .selo continua indo de ponta a ponta, porque a caixa nunca encolheu.
Uma linha resolve:
.selo { display: inline-block; }Como inline-block faz a caixa encolher até o conteúdo, ela vira um item em
linha e o text-align: center do pai passa a valer para ela também. A largura
caiu de 900 para 115,94. Se essa diferença entre block e inline-block ainda
está nebulosa, vale revisar display no CSS antes de
seguir.
Flexbox: justify-content e align-items vão no pai
Com display: flex, o pai passa a distribuir os filhos ao longo de dois eixos:
justify-content no eixo principal (horizontal, por padrão) e align-items no
eixo transversal.
.palco { display: flex; justify-content: center; align-items: center; }
.aviso { width: 240px; padding: 20px; }Centralizou na horizontal e não fez absolutamente nada na vertical. O motivo é o
mesmo do margin-inline: auto sem width: o .palco não tem altura própria,
então ele encolheu até a altura exata do .aviso — 62,39px nos dois. Sobra
zero acima e zero abaixo.
Dê altura ao pai e o alinhamento vertical passa a ter matéria-prima:
.palco { display: flex; justify-content: center; align-items: center; min-height: 300px; }
.aviso { width: 240px; padding: 20px; }Guarde essa regra: alinhamento vertical só acontece quando existe espaço vertical de sobra. Ela explica nove de cada dez “meu align-items não funciona”.
O erro mais comum: o flex no filho em vez do pai
O segundo tropeço é colocar as três propriedades no elemento errado. O
display: flex entra no .aviso, que é o filho, em vez de entrar no .palco,
que é o pai.
.palco { width: 900px; height: 300px; }
.aviso { display: flex; justify-content: center; align-items: center; height: 89px; }O aviso ficou colado no canto superior esquerdo, com 900 de largura. O que ele centralizou foi o próprio texto lá dentro. Movendo as três propriedades uma tag para cima:
.palco { width: 900px; height: 300px; display: flex; justify-content: center; align-items: center; }
.aviso { height: 89px; }Dois centros iguais, e a largura caiu para 150,34 — porque item de flex encolhe até o conteúdo em vez de esticar na horizontal. A frase para decorar é curta: quem centraliza é o pai. Os detalhes dos dois eixos estão em Flexbox no CSS.
Grid: place-items: center resolve em uma linha
place-items é o atalho de align-items e justify-items. Em uma grade de uma
célula só, ele centraliza nos dois eixos de uma vez. Mas repare no que acontece
sem ele:
.palco { display: grid; min-height: 300px; }
.aviso { padding: 20px; }Os dois centros batem em (450, 150) — e ainda assim nada está centralizado. O padrão do Grid é esticar o item até preencher a célula, então o aviso virou um retângulo de 900 × 300. Centro coincidente não é prova de nada: você precisa olhar a largura e a altura junto.
.palco { display: grid; place-items: center; min-height: 300px; }
.aviso { padding: 20px; }Agora sim: a caixa encolheu para 190,34 × 62,39 e foi parar no meio. É a forma mais curta de todas — duas declarações no pai, mais a altura. Se você ainda não mexeu com grade, comece por CSS Grid.
position: absolute com transform: translate(-50%, -50%)
Esta é a técnica que aparece em todo tutorial antigo, e ela tem uma sutileza que
quase ninguém explica. top: 50% posiciona o canto superior esquerdo do
elemento no meio do pai, não o elemento:
.palco { position: relative; height: 300px; }
.aviso { position: absolute; top: 50%; left: 50%; padding: 20px; }O canto ficou exatamente em (450, 150), que é o centro do palco — e por isso o
centro do aviso escorregou para (545,17, 181,2). Falta puxar a caixa de volta
por metade do próprio tamanho. É isso que a porcentagem do transform faz: ela
é calculada sobre o próprio elemento, e não sobre o pai.
.palco { position: relative; height: 300px; }
.aviso { position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%); padding: 20px; }A grande vantagem: funciona sem saber o tamanho do filho. A grande
desvantagem vem daqui a duas seções. Se relative, absolute e o contexto de
posicionamento ainda são novidade, a lição de
position no CSS vem antes desta.
inset: 0 com margin: auto, a que ninguém lembra
Existe uma sexta forma, mais antiga que o Flexbox e ainda perfeitamente viva: um elemento absoluto preso nos quatro lados, com margem automática nos quatro lados. O navegador resolve a equação e sobra a mesma margem em cima, embaixo e dos dois lados.
.palco { position: relative; height: 300px; }
.aviso { position: absolute; inset: 0; margin: auto; padding: 20px; }De novo o retângulo de 900 × 300: sem width e sem height, o elemento estica
para tocar os quatro lados e não sobra margem para distribuir. É a mesma lógica
da primeira seção, agora nos dois eixos.
.palco { position: relative; height: 300px; }
.aviso { position: absolute; inset: 0; margin: auto;
width: 240px; height: 80px; padding: 20px; }(900 − 240) ÷ 2 = 330 e (300 − 80) ÷ 2 = 110. O preço é ter que declarar
largura e altura. O ganho é não depender de transform, o que ajuda quando o
elemento já usa transform para outra coisa — uma animação, por exemplo.
As seis medidas lado a lado: quatro caem no mesmo pixel
Aqui está o ponto que motiva a lição inteira. Coloquei as seis técnicas na mesma página, com o mesmo filho de 175 × 89, e mandei o navegador medir. Este é o script:
import { chromium } from 'playwright-core';
const navegador = await chromium.launch();
const pagina = await navegador.newPage({ viewport: { width: 900, height: 640 } });
await pagina.setContent(`<!doctype html><meta charset="utf-8"><style>
* { box-sizing: border-box } body { margin: 0; font: 16px/1.4 system-ui, sans-serif }
.palco { width: 900px; height: 300px; display: flex; justify-content: center; align-items: center; }
.aviso { width: 175px; height: 89px; }
</style>
<section class="palco"><div class="aviso">Consulta confirmada</div></section>`);
const medida = await pagina.$eval('.aviso', (el) => {
const r = el.getBoundingClientRect();
return { left: r.left, top: r.top, centroX: r.left + r.width / 2, centroY: r.top + r.height / 2 };
});
console.log(medida);
await navegador.close();Só npm i playwright-core não basta: esse pacote traz a API, e não o navegador.
Sem rodar npx playwright install chromium antes, o launch() para na primeira
linha:
Com o Chromium no lugar, é só trocar a regra do .palco e repetir para as seis,
em um pai de dimensões pares e em outro de dimensões ímpares:
== pai 901 x 301 (impar) centro do pai = (450.5, 150.5) tecnica left top centro x centro y erro margin-inline: auto 363 0 450.5 44.5 106.000 text-align + inline-block 363 0 450.5 44.5 106.000 flex + center 363 106 450.5 150.5 0.000 grid + place-items 363 106 450.5 150.5 0.000 absolute + transform 363 106 450.5 150.5 0.000 inset: 0 + margin: auto 363 106 450.5 150.5 0.000
As quatro técnicas de dois eixos entregam o mesmo retângulo, na mesma posição, com erro zero, em dimensão par e em dimensão ímpar. Não existe uma “mais precisa” que a outra. As duas primeiras erram por 105,5 e 106 porque só tratam do eixo horizontal — é o esperado, não um defeito.
E o rumor de que translate(-50%, -50%) deixa o texto borrado? Ele tem um fundo
de verdade minúsculo. Tire o tamanho fixo do filho, para que largura e altura
saiam do texto e caiam em número quebrado. A técnica do inset continua
precisando de medida, como a seção anterior mostrou — e max-content é
justamente a medida do texto, então as quatro ficam comparáveis:
/* nas quatro técnicas */
.aviso { padding: 20px; }
/* só na do inset, para ela também medir pelo texto */
.aviso { width: max-content; height: max-content; }O transform é o único que escapa da grade interna de 1/64 de pixel do
navegador: 118,8046875 contra 118,796875. A diferença é 1/128 de pixel. Para
saber se isso vira algo visível, tirei um print do palco em cada técnica e
comparei o hash dos arquivos. Reaproveitando a pagina do script de medição,
duas linhas bastam:
import { createHash } from 'node:crypto';
const buf = await pagina.locator('.palco').screenshot();
console.log(createHash('sha1').update(buf).digest('hex').slice(0, 16));Mesmo hash nas três: PNGs byte a byte idênticos, em tela de densidade 1. A quarta
linha é o controle — o mesmo palco sem nenhuma regra de centralização — e serve
para provar que o hash de fato muda quando a imagem muda. Nesse cenário, o
transform não borra nada. Escolha a técnica pelo que vem a seguir, não pelo
pixel.
Quando o conteúdo não cabe é que elas se separam
A diferença real aparece no dia em que o conteúdo cresce. Pai com
min-height: 160px, aviso com 260px de altura:
Flex e Grid empurraram o pai de 160 para 260: o aviso continua inteiro e nada
vazou. As duas técnicas com position: absolute mantiveram o pai em 160 e
deixaram 50px para fora em cima e 50px embaixo — 100px de conteúdo escapando da
caixa. É a consequência de tirar o elemento do fluxo: ele deixa de empurrar
quem está em volta.
Só que o Flexbox tem uma armadilha própria aqui. Trave a altura do pai em 160px,
ligue overflow: auto, e o conteúdo que sai por cima fica inalcançável:
.palco { width: 900px; height: 160px; overflow: auto;
display: flex; justify-content: center; align-items: center; }
.aviso { width: 240px; height: 260px; }Com center, o scrollHeight é 210 e a rolagem para em 50 — os 50px de cima
existem, mas nenhum scroll chega neles. Com safe center, o navegador desiste
de centralizar quando isso causaria estouro e alinha no começo: scrollHeight
vira 260 e o topo fica acessível.
.palco { width: 900px; height: 160px; overflow: auto;
display: flex; justify-content: center; align-items: safe center; }Uma palavra a mais, e o conteúdo para de sumir.
Centralizar quando a altura do pai é desconhecida
O caso mais frequente na vida real é centralizar na tela inteira: login, tela de
carregamento, erro 404. O reflexo de muita gente é height: 100%, e ele falha:
.tela { height: 100%; display: grid; place-items: center; }Sessenta e dois pixels, numa janela de 640. Porcentagem de altura precisa de um
pai com altura definida, e nem html nem body têm. A unidade de viewport
não depende de ninguém:
.tela { min-height: 100dvh; display: grid; place-items: center; }Use min-height, não height: se o conteúdo passar da tela, a caixa cresce em
vez de cortar. A diferença entre vh, dvh e svh — e por que a barra de
endereço do celular importa aqui — está em
unidades no CSS.
Para um modal, junte position: fixed com inset: 0 e o mesmo
place-items: center. O fixed ancora na janela, então a cortina cobre a tela
mesmo com a página rolada:
.cortina { position: fixed; inset: 0; display: grid; place-items: center;
background: rgba(0, 0, 0, .5); }
.modal { width: 320px; max-height: calc(100dvh - 32px); overflow: auto; padding: 24px; }Atrás da cortina havia um main de 2000px de altura, e a página estava rolada
900px quando a medida foi tirada. O modal continuou no meio da janela. O
max-height com overflow: auto é o que salva o modal quando o texto dentro
dele cresce — sem isso, você cai exatamente no problema da seção anterior.
Tabela de decisão: qual das seis usar em cada caso
| situação | técnica | por que essa |
|---|---|---|
| conteúdo de página numa coluna legível | max-width + margin-inline: auto |
não mexe no display do pai nem exige container |
| logo, botão ou texto dentro de uma barra | text-align: center no pai |
o filho já é inline; não precisa de container |
| card no meio de uma área que pode crescer | display: flex + justify-content + align-items |
o pai cresce junto com o conteúdo |
| tela cheia: login, 404, carregando | min-height: 100dvh + place-items: center |
duas linhas e não depende da altura de html |
| selo por cima de uma imagem, tamanho desconhecido | absolute + translate(-50%, -50%) |
não exige saber largura nem altura do filho |
elemento absoluto que já usa transform |
absolute + inset: 0 + margin: auto |
deixa o transform livre para a animação |
| área rolável com altura travada | flex + align-items: safe center |
variante da terceira linha: não esconde o topo no estouro |
A pergunta que decide, na prática, é sempre a mesma: o pai pode crescer? Se
pode, Flex ou Grid. Se não pode e o filho precisa flutuar por cima do resto,
position: absolute.
O que treinar agora
Abra o painel da Pata Amiga com um .palco de min-height: 320px e um aviso
dentro. Centralize com Grid, meça o getBoundingClientRect pelo console do
DevTools e confira se os dois centros batem. Depois coloque um parágrafo de
cinco linhas dentro do aviso e veja o pai crescer. Repita com
position: absolute e observe o conteúdo vazando.
O próximo passo da trilha de CSS é
justify-content e align-items, que
abre o que center esconde: space-between, space-around, start e end.
O mapa completo da linguagem está no
guia de CSS.
Prefere aprender em vídeo?
Tem uma aula sobre este assunto no nosso canal.
Perguntas frequentes
margin, 0 auto e margin-inline, auto são a mesma coisa?
margin: 0 auto também zera as margens de cima e de baixo, o que pode apagar um espaçamento que você queria. E margin-inline mira o eixo do texto, não o eixo horizontal: em português ou em árabe os dois dão no mesmo, porque a escrita é horizontal nas duas. Num pai com writing-mode: vertical-rl a diferença aparece — medindo um filho de 320 × 80 num pai de 900 × 400, margin: 0 auto deixa o topo em 0 e margin-inline: auto centraliza a caixa, com o topo em 160.Qual a diferença entre place-items e place-content no Grid?
place-items alinha cada item dentro da célula em que ele caiu. place-content move a grade inteira dentro do container, e só faz efeito quando as faixas somam menos que o tamanho do pai. Com uma célula só e um item sem tamanho fixo, os dois parecem iguais; com várias colunas, não.Preciso de position, relative no pai para usar absolute?
top: 50% conta a partir do bloco contentor inicial, que tem o tamanho da **janela** ancorado no topo do documento — e não a altura do documento. Num documento de 3000px, janela de 640px e o cartão começando em 1200px, o aviso para em 320px do topo do documento; com position: relative no cartão, ele vai para 1350px, dentro do cartão.Como centralizo uma imagem dentro de uma div?
img é um elemento inline, então text-align: center no pai já resolve. Se você preferir tratar a imagem como bloco, use display: block; margin-inline: auto — aí ela obedece às mesmas regras de qualquer caixa.Centralizar com largura fixa quebra no celular?
width: 320px por max-width: 320px: a caixa encolhe junto com a tela e a margem automática continua absorvendo a sobra, sem media query nenhuma.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), e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — CSS box alignment — developer.mozilla.org
- CSS Box Alignment Module Level 3 — w3.org
- CSS 2.1 — Absolutely positioned, non-replaced elements — w3.org



