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

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.

Rodolfo Mori13 min de leitura

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.

html
<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:

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

css
.pagina { width: 900px; }
.cartao { width: 320px; margin-inline: auto; padding: 16px; }
com width: 320px .cartao left=290 width=320 right=290 (sobra à direita)

Sobraram 900 − 320 = 580 pixels, e cada lado ficou com 290. Agora tire a largura e rode de novo:

css
.pagina { width: 900px; }
.cartao { margin-inline: auto; padding: 16px; }
sem width .cartao left=0 width=900 right=0 (sobra à direita)

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.

html
<header class="topo">
  <div class="selo"><strong class="marca">Pata Amiga</strong></div>
</header>
css
.topo { width: 900px; text-align: center; }
.selo { border: 2px solid #333; padding: 12px; }
só text-align: center no pai .selo left=0 width=900 centro=450 .marca left=406.03 width=87.94 centro=450

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:

css
.selo { display: inline-block; }
+ display: inline-block no filho .selo left=392.03 width=115.94 centro=450 .marca left=406.03 width=87.94 centro=450

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.

css
.palco { display: flex; justify-content: center; align-items: center; }
.aviso { width: 240px; padding: 20px; }
sem altura no pai .palco height=62.39 centro vertical=31.2 .aviso top=0 height=62.39 centro vertical=31.2 sobra acima=0 sobra abaixo=0

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.

.palco sem altura sobra = 0 .palco com 300px 118,8 118,8

Dê altura ao pai e o alinhamento vertical passa a ter matéria-prima:

css
.palco { display: flex; justify-content: center; align-items: center; min-height: 300px; }
.aviso { width: 240px; padding: 20px; }
com min-height: 300px .palco height=300 centro vertical=150 .aviso top=118.8 height=62.39 centro vertical=149.99 sobra acima=118.8 sobra abaixo=118.81

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.

css
.palco { width: 900px; height: 300px; }
.aviso { display: flex; justify-content: center; align-items: center; height: 89px; }
flex no .aviso (o filho) .aviso left=0 top=0 width=900 height=89 centro do aviso=(450, 44.5) centro do palco=(450, 150)

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:

css
.palco { width: 900px; height: 300px; display: flex; justify-content: center; align-items: center; }
.aviso { height: 89px; }
flex no .palco (o pai) .aviso left=374.83 top=105.5 width=150.34 height=89 centro do aviso=(450, 150) centro do palco=(450, 150)

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:

css
.palco { display: grid; min-height: 300px; }
.aviso { padding: 20px; }
display: grid, sem place-items .aviso left=0 top=0 width=900 height=300 centro do pai=(450, 150) centro do aviso=(450, 150)

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.

css
.palco { display: grid; place-items: center; min-height: 300px; }
.aviso { padding: 20px; }
+ place-items: center .aviso left=354.83 top=118.8 width=190.34 height=62.39 centro do pai=(450, 150) centro do aviso=(450, 149.99)

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:

css
.palco { position: relative; height: 300px; }
.aviso { position: absolute; top: 50%; left: 50%; padding: 20px; }
top/left 50% sem transform .aviso left=450 top=150 width=190.34 height=62.39 centro do pai=(450, 150) centro do aviso=(545.17, 181.2)

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.

css
.palco { position: relative; height: 300px; }
.aviso { position: absolute; top: 50%; left: 50%;
         transform: translate(-50%, -50%); padding: 20px; }
+ transform: translate(-50%, -50%) .aviso left=354.83 top=118.8 width=190.34 height=62.39 centro do pai=(450, 150) centro do aviso=(450, 150)

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.

css
.palco { position: relative; height: 300px; }
.aviso { position: absolute; inset: 0; margin: auto; padding: 20px; }
inset: 0 + margin: auto, sem medida .aviso left=0 top=0 width=900 height=300 centro do pai=(450, 150) centro do aviso=(450, 150)

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.

css
.palco { position: relative; height: 300px; }
.aviso { position: absolute; inset: 0; margin: auto;
         width: 240px; height: 80px; padding: 20px; }
+ width e height .aviso left=330 top=110 width=240 height=80 centro do pai=(450, 150) centro do aviso=(450, 150)

(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:

js
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();
{ left: 362.5, top: 105.5, centroX: 450, centroY: 150 }

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:

browserType.launch: Executable doesn't exist at /…/chromium_headless_shell-1234/chrome-headless-shell-mac-arm64/chrome-headless-shell ╔════════════════════════════════════════════════════════════╗ ║ Looks like Playwright was just installed or updated. ║ ║ Please run the following command to download new browsers: ║ ║ ║ ║ npx playwright install ║ ║ ║ ║ <3 Playwright Team ║ ╚════════════════════════════════════════════════════════════╝

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 900 x 300 (par) centro do pai = (450, 150) tecnica left top centro x centro y erro margin-inline: auto 362.5 0 450 44.5 105.500 text-align + inline-block 362.5 0 450 44.5 105.500 flex + center 362.5 105.5 450 150 0.000 grid + place-items 362.5 105.5 450 150 0.000 absolute + transform 362.5 105.5 450 150 0.000 inset: 0 + margin: auto 362.5 105.5 450 150 0.000

== 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:

css
/* 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; }
aviso sem medida fixa: largura e altura vem do texto (fracionarias) tecnica left top largura flex + center 354.828125 118.796875 190.34375 grid + place-items 354.828125 118.796875 190.34375 absolute + transform 354.828125 118.8046875 190.34375 inset: 0 + margin: auto 354.828125 118.796875 190.34375

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:

js
import { createHash } from 'node:crypto';

const buf = await pagina.locator('.palco').screenshot();
console.log(createHash('sha1').update(buf).digest('hex').slice(0, 16));
flex + center 7258fb2ac2de2eb5 grid + place-items 7258fb2ac2de2eb5 absolute + transform 7258fb2ac2de2eb5 controle: sem centralizar 210a52824639a801

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:

pai com min-height: 160px, aviso com 260px de altura tecnica altura do pai topo do aviso sobra fora do pai flex + center 260 0 0 grid + place-items 260 0 0 absolute + transform 160 -50 100 inset: 0 + margin: auto 160 -50 100

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:

css
.palco { width: 900px; height: 160px; overflow: auto;
         display: flex; justify-content: center; align-items: center; }
.aviso { width: 240px; height: 260px; }
pai: height 160px + overflow auto | aviso: 260px de altura tecnica scrollHeight scrollTop max topo do aviso alcancavel? align-items: center 210 50 -50 nao align-items: safe center 260 100 0 sim

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.

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

css
.tela { height: 100%; display: grid; place-items: center; }
.tela { height: 100% } .tela height=62.39 (viewport = 640) .aviso top=0 centro vertical=31.2

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:

css
.tela { min-height: 100dvh; display: grid; place-items: center; }
.tela { min-height: 100dvh } .tela height=640 (viewport = 640) .aviso top=288.8 centro vertical=319.99

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:

css
.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; }
scrollY = 900 viewport = 900 x 640 .cortina left=0 top=0 width=900 height=640 .modal left=290 top=273.61 width=320 height=92.78 centro do modal = (450, 320) centro da tela = (450, 320)

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.

Ver todos os vídeos do canal
  • css
  • centralizar
  • flexbox
  • grid
  • alinhamento

Perguntas frequentes

margin, 0 auto e margin-inline, auto são a mesma coisa?
Quase. 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?
Precisa se o pai for a referência que você quer. Sem nenhum ancestral posicionado, o 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?
Quebra se a largura fixa for maior que a tela. Troque 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.

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

  1. MDN — CSS box alignment — developer.mozilla.org
  2. CSS Box Alignment Module Level 3 — w3.org
  3. CSS 2.1 — Absolutely positioned, non-replaced elements — w3.org

Continue por aqui