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

display no CSS: block, inline, inline-block e none

O que cada valor de display muda em largura, altura e quebra de linha, por que padding em inline não empurra nada e o que none faz que hidden não faz.

Rodolfo Mori15 min de leitura

display responde duas perguntas ao mesmo tempo: como o elemento se comporta por fora, na linha em que ele nasce, e como ele arruma os filhos por dentro. É por isso que uma <div> empurra tudo para baixo e um <span> não, mesmo os dois recebendo exatamente o mesmo CSS.

Os exemplos são todos da mesma página: a agenda da Clínica Veterinária Pata Firme. Cada número desta lição saiu de uma medição real — getBoundingClientRect() e getComputedStyle() rodando num Chromium 151.0.7922.34 sem interface, dirigido pelo Playwright 1.62.1. Nenhuma medida aqui é estimativa.

A lição de seletores e cascata mostrou como a regra chega até o elemento. display é a primeira decisão que essa regra toma depois de chegar: que tipo de caixa esse elemento vai virar.

O fluxo normal: por que a div ocupa a linha inteira sem você pedir

Sem nenhum CSS de layout, o navegador coloca as caixas no que a especificação chama de fluxo normal: de cima para baixo, uma abaixo da outra, quando a caixa é de bloco; e da esquerda para a direita, dentro da linha de texto, quando a caixa é inline.

Estes quatro elementos têm o mesmo texto e o mesmo pai de 600px:

html
<section class="painel">
  <div class="servico">Consulta</div>
  <div class="servico">Vacina</div>
  <span class="etiqueta">Consulta</span>
  <span class="etiqueta">Vacina</span>
</section>

O CSS não diz nada sobre tamanho — só pinta o fundo para dar de ver a caixa:

css
.painel { width: 600px; }
.servico { background: #eee; }
.etiqueta { background: #ddd; }

Esta é a base usada em todas as medições da lição, e o .painel de 600px acima continua valendo do começo ao fim. Vale anotar, porque os números decimais que vão aparecer saem dela:

css
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: system-ui, sans-serif;
  font-size: 18px;
  line-height: 1.2;
}

E este é o script de medição, que se repete o resto da lição inteira:

js
const medir = (rotulo, sel, i) => {
  const b = document.querySelectorAll(sel)[i].getBoundingClientRect();
  console.log(`${rotulo} -> largura ${b.width.toFixed(2)} | topo ${b.y}`);
};

console.log('largura do painel:', document.querySelector('.painel').getBoundingClientRect().width);
medir('div  "Consulta"', '.servico', 0);
medir('div  "Vacina"  ', '.servico', 1);
medir('span "Consulta"', '.etiqueta', 0);
medir('span "Vacina"  ', '.etiqueta', 1);
largura do painel: 600 div "Consulta" -> largura 600.00 | topo 0 div "Vacina" -> largura 600.00 | topo 21.59375 span "Consulta" -> largura 71.11 | topo 43.1875 span "Vacina" -> largura 53.23 | topo 43.1875

Leia os quatro números de topo. As duas <div> estão em linhas diferentes (0 e 21,59 — a altura de uma linha, 18px × 1,2) e cada uma esticou até os 600px do pai. Os dois <span> estão na mesma linha (43,1875 os dois) e cada um mediu exatamente o próprio texto: 71,11px para “Consulta”, 53,23px para “Vacina”.

Ninguém escreveu display em lugar nenhum. <div> e <section> já nascem display: block pela folha de estilo do navegador; <span>, <a> e <strong> já nascem display: inline. display é a propriedade que troca esse padrão.

block aceita width, height e margem vertical

Numa caixa de bloco, as quatro medidas que você escreve valem. É o modo previsível, e é onde quase todo layout de página começa.

html
<section class="painel">
  <div class="cartao" id="a">Consulta clínica</div>
  <div class="cartao" id="b">Vacina antirrábica</div>
</section>
css
.cartao {
  width: 320px;
  height: 90px;
  margin: 24px 0;
  background: #eee;
}
js
const a = document.querySelector('#a').getBoundingClientRect();
const b = document.querySelector('#b').getBoundingClientRect();

console.log('cartão A: largura', a.width, '| altura', a.height);
console.log('cartão A vai de y =', a.top, 'até y =', a.bottom);
console.log('cartão B começa em y =', b.top);
console.log('espaço entre eles:', b.top - a.bottom);
console.log('altura do painel:', document.querySelector('.painel').getBoundingClientRect().height);
cartão A: largura 320 | altura 90 cartão A vai de y = 24 até y = 114 cartão B começa em y = 138 espaço entre eles: 24 altura do painel: 204

width: 320px virou 320px de largura, height: 90px virou 90px de altura, e o cartão parou de ocupar a linha inteira porque você deu uma largura menor que a do pai. Isso é a caixa de bloco fazendo o que foi mandada.

Repare no espaço entre os dois cartões: 24px, não 48. Cada cartão pediu 24px embaixo e 24 em cima, e o navegador juntou os dois num só. Esse é o colapso de margem, um comportamento exclusivo das margens verticais no fluxo normal.

Ele acontece duas vezes nesta medição, e é a soma das duas que dá os 204px do painel. A primeira é a que você acabou de ver, entre os dois irmãos: 24 e 24 viram 24, e o total sai dos 276px que a conta pediria (180 de altura mais 96 de margem) para 252. A segunda é mais silenciosa — a margem de cima do primeiro cartão e a de baixo do último escapam do painel, porque ele não tem borda nem padding para segurar; é por isso que o painel começa em y = 24 e mede 90 + 24 + 90 = 204. O assunto tem lição própria em margin-top não funciona; o que interessa agora é que margin vertical existe aqui. Guarde isso para a próxima seção.

Quanto ao width: 320px virar 320px na tela, e não 320 mais padding e borda, o responsável é o box-sizing — assunto do box model, não do display.

inline: onde width, height e margem vertical viram enfeite

Esta é a seção que resolve a maior parte das dúvidas sobre display. É também o erro mais comum de quem está começando: escrever height num <span> e não entender por que nada acontece.

O aviso da clínica tem um selo no meio da frase:

html
<p class="aviso">A <span class="selo">antirrábica</span> é obrigatória.</p>
<p class="depois">Agende pelo telefone da recepção.</p>
css
.aviso, .depois { margin: 0; }
.selo { padding: 30px; background: #ddd; }

A margem dos dois parágrafos está zerada de propósito: assim o número que sobrar entre eles é obra do selo, e de mais nada.

30px de padding em volta de um texto de 21px deveria dar uma caixa de uns 81px de altura, e o parágrafo deveria crescer junto. Metade disso acontece:

js
const rect = (s) => document.querySelector(s).getBoundingClientRect();

console.log('caixa pintada do selo:', rect('.selo').width.toFixed(2), 'x', rect('.selo').height.toFixed(2));
console.log('altura do parágrafo que contém o selo:', rect('.aviso').height.toFixed(2));
console.log('topo do parágrafo seguinte:', rect('.depois').top.toFixed(2));
caixa pintada do selo: 145.39 x 81.00 altura do parágrafo que contém o selo: 21.59 topo do parágrafo seguinte: 21.59

Aí está o defeito inteiro em três números. A caixa do selo tem 81px de altura — o padding existe e é pintado. Mas o parágrafo que contém o selo continua com 21,59px, e o parágrafo seguinte continua começando em 21,59.

Ou seja: o fundo cinza do selo vaza por cima e por baixo da linha, passa por trás do texto vizinho, e ninguém sai do lugar para acomodar. Numa caixa inline, o padding vertical é desenhado, mas não é contado na altura da linha.

Agora as três declarações que simplesmente não têm efeito. Cada linha da saída é uma medição feita depois de acrescentar mais um bloco de CSS ao mesmo selo:

css
/* 1º: só o fundo            */ .selo { background: #ddd; }
/* 2º: + tamanho explícito   */ .selo { width: 400px; height: 200px; }
/* 3º: + margem vertical     */ .selo { margin: 40px 0; }
/* 4º: + margem nos 4 lados  */ .selo { margin: 40px; }
só com background -> largura 85.39 | altura 21.00 | esquerda 16.22 | parágrafo de baixo em 21.59 + width 400 height 200 -> largura 85.39 | altura 21.00 | esquerda 16.22 | parágrafo de baixo em 21.59 + margin 40px 0 -> largura 85.39 | altura 21.00 | esquerda 16.22 | parágrafo de baixo em 21.59 + margin 40px (4 lados)-> largura 85.39 | altura 21.00 | esquerda 56.22 | parágrafo de baixo em 21.59

Quatro linhas, e só um número mudou no fim: a coluna esquerda, de 16,22 para 56,22 — os 40px de margem horizontal, que valem. width, height e a margem vertical não moveram um pixel.

Por que a imagem e o botão parecem escapar da regra

Um detalhe que confunde: <img> computa display: inline e mesmo assim aceita width e height. A explicação não está no display — está no fato de a imagem ser um elemento substituído, uma caixa cujo conteúdo vem de fora do documento e por isso tem dimensão própria.

Quatro elementos diferentes na mesma linha do formulário de peso, todos com o mesmo CSS:

html
<span class="rotulo">Peso</span>
<img class="icone" alt="" src="balanca.svg">
<input class="campo" value="12,4 kg">
<button class="acao">Salvar</button>
css
.rotulo, .icone, .campo, .acao { width: 120px; height: 60px; }
.rotulo display: inline caixa: 39.56 x 21.00 .icone display: inline caixa: 120.00 x 60.00 .campo display: inline-block caixa: 120.00 x 60.00 .acao display: inline-block caixa: 120.00 x 60.00

.rotulo é um <span>: ignorou as duas medidas. .icone é um <img>: inline e obedeceu. .campo (<input>) e .acao (<button>) nem são inline — o navegador já os entrega como inline-block, que é o próximo valor.

O erro que o navegador não avisa

O CSS não tem console de erro. Um valor com typo é descartado em silêncio, e o elemento fica com o valor anterior:

js
const rotulo = document.querySelector('.rotulo');

rotulo.style.display = 'inline-blok';
console.log('style.display = "inline-blok"  -> computado:', getComputedStyle(rotulo).display);

rotulo.style.display = 'inline-block';
console.log('style.display = "inline-block" -> computado:', getComputedStyle(rotulo).display);

console.log('CSS.supports:', JSON.stringify({
  'inline flex': CSS.supports('display', 'inline flex'),
  'flow-root': CSS.supports('display', 'flow-root'),
  contents: CSS.supports('display', 'contents'),
  'inline-blok': CSS.supports('display', 'inline-blok'),
}));
style.display = "inline-blok" -> computado: inline style.display = "inline-block" -> computado: inline-block CSS.supports: {"inline flex":true,"flow-root":true,"contents":true,"inline-blok":false}

Nenhum aviso, nenhuma exceção: só o inline antigo continuando de pé. Quando um display “não faz nada”, CSS.supports('display', 'o-valor') responde em um segundo se o problema é o navegador ou o seu teclado.

inline-block: o meio-termo, e o vão de 4,56px que ninguém pediu

inline-block é literalmente o acordo entre os dois: por fora ele continua na linha, ao lado do texto; por dentro ele vira uma caixa de bloco de verdade, com largura, altura e margem funcionando.

Mesmo selo, mesmo padding de 30px, só trocando uma linha:

css
.selo { padding: 30px; background: #ddd; display: inline-block; }
display: inline -> selo 145.39x81.00 | parágrafo 21.59 | vizinho de baixo em 21.59 display: inline-block -> selo 145.39x81.59 | parágrafo 81.59 | vizinho de baixo em 81.59

o vizinho de baixo desceu 60.00 px

O parágrafo saiu de 21,59 para 81,59px de altura, e o parágrafo seguinte desceu exatamente 60px — os dois paddings de 30. O padding parou de vazar e passou a ocupar espaço. É a mesma declaração de antes; o que mudou foi o tipo de caixa que a recebeu.

Agora o efeito colateral clássico. Três etiquetas de serviço, uma por linha no arquivo, do jeito que todo mundo escreve:

html
<div class="linha">
  <span class="tag">Banho</span>
  <span class="tag">Tosa</span>
  <span class="tag">Vacina</span>
</div>
css
.tag { display: inline-block; padding: 8px 16px; background: #ddd; }
HTML com quebra de linha entre as tags -> vãos: 4.56px, 4.56px HTML sem espaço nenhum entre as tags -> vãos: 0px, 0px

Existe um vão de 4,56px entre as etiquetas que ninguém escreveu no CSS. Ele não é margem: é o espaço em branco do HTML. A quebra de linha entre os <span> é um caractere de espaço, e como as caixas continuam na linha por fora, esse espaço é renderizado igual a qualquer outro espaço de texto. 4,56px é a largura de um espaço nesta fonte, neste tamanho.

Grudar as tags numa linha só resolve, mas ninguém quer escrever HTML assim. Duas saídas melhores:

css
/* gambiarra clássica: zera a fonte no pai e devolve nos filhos */
.linha { font-size: 0; }
.tag { font-size: 18px; }

/* solução de hoje: o pai deixa de montar linhas de texto */
.linha { display: flex; gap: 12px; }
com font-size: 0 no pai -> vãos: 0px, 0px trocando o pai para flex com gap: 12px -> vãos: 12px, 12px

Com display: flex o vão fantasma some e o espaçamento passa a ser um número que você escolheu, não um resíduo do arquivo. É o motivo de Flexbox ter aposentado boa parte do uso de inline-block em barras de botões e listas de etiquetas.

none, visibility: hidden e opacity: 0 não são sinônimos

As três “escondem”. Só que escondem coisas diferentes, e trocar uma pela outra muda o layout, o clique e o leitor de tela. Este é o banner de feriado da clínica, medido nas três versões. A mesma seção aparece três vezes na página, mudando só o id:

html
<section class="painel bloco" id="none">
  <div class="banner">Unidade fechada no feriado</div>
  <p class="texto">Plantão 24h pelo WhatsApp.</p>
</section>
<!-- a mesma seção repetida com id="hidden" e com id="opacidade" -->
css
.bloco { border: 1px solid #999; }
.banner { height: 60px; background: #ddd; }
.texto { margin: 0; }

#none .banner      { display: none; }
#hidden .banner    { visibility: hidden; }
#opacidade .banner { opacity: 0; }
js
const rotulo = {
  none: 'display: none',
  hidden: 'visibility: hidden',
  opacidade: 'opacity: 0',
};

for (const id of ['none', 'hidden', 'opacidade']) {
  const secao = document.querySelector('#' + id);
  const banner = secao.querySelector('.banner');
  const b = banner.getBoundingClientRect();
  const s = secao.getBoundingClientRect();
  const alvo = document.elementFromPoint(s.left + 100, s.top + 30);
  const pai = banner.offsetParent;

  console.log(rotulo[id]);
  console.log(`  caixa do banner: ${b.width} x ${b.height}`);
  console.log(`  offsetParent: ${pai === null ? 'null' : pai.tagName.toLowerCase()}`);
  console.log(`  altura da seção: ${s.height.toFixed(2)}`);
  console.log(`  elementFromPoint no banner: .${alvo.className.split(' ')[0]}`);
  console.log(`  banner.innerText: ${JSON.stringify(banner.innerText)}`);
}
display: none caixa do banner: 0 x 0 offsetParent: null altura da seção: 23.59 elementFromPoint no banner: .painel banner.innerText: "Unidade fechada no feriado" visibility: hidden caixa do banner: 598 x 60 offsetParent: body altura da seção: 83.59 elementFromPoint no banner: .painel banner.innerText: "" opacity: 0 caixa do banner: 598 x 60 offsetParent: body altura da seção: 83.59 elementFromPoint no banner: .banner banner.innerText: "Unidade fechada no feriado"

Três diferenças que importam no dia a dia:

display: none visibility: hidden opacity: 0
ocupa espaço não (seção com 23,59px) sim (83,59px) sim (83,59px)
tem caixa não (0 x 0, offsetParent nulo) sim sim
recebe clique não não sim
é lido pelo leitor de tela não não sim

O elementFromPoint da terceira linha é o detalhe que gera bug de produção: com opacity: 0 o banner continua lá, invisível, roubando o clique de quem estiver embaixo. É o “botão que não funciona” que você vai caçar por meia hora.

O innerText também separa os três, e de um jeito que pega gente no JavaScript: ele devolve string vazia só no visibility: hidden, porque lê o que está renderizado. No display: none não existe nada renderizado para ler, então ele cai no texto cru do elemento e devolve a frase inteira.

A última linha da tabela veio de outra medição, na camada que quase ninguém confere: a árvore de acessibilidade, que é o que o leitor de tela anuncia. Quatro botões da agenda dentro de um <nav>, três deles escondidos de um jeito diferente:

html
<nav class="painel">
  <button class="none">Agendar banho</button>
  <button class="hidden">Agendar tosa</button>
  <button class="opacidade">Agendar vacina</button>
  <button>Falar com a recepção</button>
</nav>
css
.none { display: none; }
.hidden { visibility: hidden; }
.opacidade { opacity: 0; }

Quem lê a árvore aqui é o ariaSnapshot() do Playwright sobre o <body> — o mesmo mapa que o leitor de tela percorre:

- navigation: - button "Agendar vacina" - button "Falar com a recepção"

display: none e visibility: hidden tiraram os botões da árvore. opacity: 0 não: “Agendar vacina” continua lá, anunciado para quem usa leitor de tela e invisível para quem usa os olhos.

display: contents e o dia em que ele salva um grid

Grid e Flexbox só enxergam os filhos diretos. Quando um componente envelopa dois cartões numa <div>, essa <div> vira um único item da grade e o layout desanda. É exatamente o caso da grade de serviços da clínica:

html
<div class="grade">
  <article class="cartao">Consulta</article>
  <div class="grupo">
    <article class="cartao">Banho</article>
    <article class="cartao">Tosa</article>
  </div>
  <article class="cartao">Vacina</article>
</div>
css
.grade { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; width: 620px; }
.cartao { background: #ddd; padding: 10px; }
.grupo { border: 2px solid #333; }

display: contents apaga a caixa do elemento e promove os filhos ao lugar dele. Uma linha:

css
.grupo { display: contents; }

A medição percorre os quatro cartões e ainda mede a caixa do envelope:

js
for (const c of document.querySelectorAll('.cartao')) {
  const r = c.getBoundingClientRect();
  console.log(`  ${c.textContent.padEnd(9)} x=${r.left.toFixed(0).padStart(3)}  y=${r.top.toFixed(0).padStart(3)}  largura=${r.width.toFixed(0)}`);
}

const g = document.querySelector('.grupo').getBoundingClientRect();
console.log(`  caixa da .grupo: ${g.width.toFixed(0)} x ${g.height.toFixed(0)}`);
--- .grupo é uma div normal --- Consulta x= 0 y= 0 largura=200 Banho x=212 y= 2 largura=196 Tosa x=212 y= 44 largura=196 Vacina x=420 y= 0 largura=200 caixa da .grupo: 200 x 87 --- .grupo { display: contents } --- Consulta x= 0 y= 0 largura=200 Banho x=210 y= 0 largura=200 Tosa x=420 y= 0 largura=200 Vacina x= 0 y= 52 largura=200 caixa da .grupo: 0 x 0

Sem contents, “Banho” e “Tosa” estão empilhados na mesma coluna (x=212 os dois) e sobra uma coluna vazia. Com contents, os quatro cartões viram itens da grade: três na primeira linha e “Vacina” quebrando para a segunda, em y=52.

O preço está na última linha da primeira medição: a caixa da .grupo saiu de 200 x 87 para 0 x 0. Não existe mais caixa — então a borda de 2px que ela tinha sumiu junto, e qualquer background, padding ou box-shadow no envelope some também. display: contents não é um jeito de esconder: é um jeito de desaparecer com a caixa e manter o conteúdo.

Mais sobre montar a grade em si na lição de CSS Grid.

O display de duas palavras: inline flex é inline-flex

block, inline, inline-block e flex parecem quatro coisas independentes. Não são. A especificação define display como duas informações separadas: o tipo de caixa por fora e o tipo de contexto de formatação por dentro.

por fora por dentro inline flow-root fica na linha, ao lado do texto empilha os filhos como blocos as duas juntas = inline-block

Os navegadores modernos aceitam essa sintaxe de duas palavras, e traduzem para o nome antigo na hora de computar. Onze valores escritos, e o que o Chromium devolve em getComputedStyle:

css
#t0 { display: block flow; }
#t1 { display: inline flow; }
#t2 { display: block flow-root; }
#t3 { display: inline flow-root; }
#t4 { display: block flex; }
#t5 { display: inline flex; }
#t6 { display: block grid; }
#t7 { display: inline grid; }
#t8 { display: block flow list-item; }
#t9 { display: contents; }
#t10 { display: none; }
display: block flow -> computado: block display: inline flow -> computado: inline display: block flow-root -> computado: flow-root display: inline flow-root -> computado: inline-block display: block flex -> computado: flex display: inline flex -> computado: inline-flex display: block grid -> computado: grid display: inline grid -> computado: inline-grid display: block flow list-item -> computado: list-item display: contents -> computado: contents display: none -> computado: none

A quarta linha é a chave da lição inteira: inline flow-root computa como inline-block. O inline-block que você usa desde sempre não é um valor especial — é “inline por fora, caixa de bloco independente por dentro”. E flex, que a gente costuma pensar como o oposto de block, é block flex: por fora ele é um bloco igual a qualquer <div>; o que muda é só o lado de dentro.

Esse par também explica um comportamento que assusta: pôr um <span> dentro de um contêiner flex faz ele parar de ser inline sozinho.

html
<div class="flexpai"><span id="s1">Banho</span><span id="s2">Tosa</span></div>
<div><span id="s3">Vacina</span></div>
css
.flexpai { display: flex; }
#s1, #s2, #s3 { width: 150px; height: 40px; background: #ddd; }
span dentro de flex (#s1) -> block caixa: 150.00 x 40.00 span fora de flex (#s3) -> inline caixa: 53.23 x 21.00

O mesmo <span>, com o mesmo CSS: dentro do flex ele computa block e obedece a width e height; fora, continua inline e ignora as duas. O contêiner flex força a parte “por fora” dos filhos para block — e é por isso que width funciona em item de flex sem você ter escrito display nenhum.

flow-root: o clearfix que virou propriedade

Sobra um valor que quase ninguém ensina e que resolve um problema antigo. A ficha do paciente tem a foto flutuando à esquerda:

html
<article class="ficha">
  <div class="foto"></div>
  <p class="nome">Thor, 4 anos</p>
</article>
<p class="abaixo">Retorno em 15 dias.</p>
css
.ficha { border: 2px solid #333; width: 400px; }
.foto { float: left; width: 100px; height: 100px; background: #ddd; }
.nome { margin: 0; }
sem nada: altura da ficha: 26px | base da foto: 102px | parágrafo de baixo em: 43.59375px com overflow: hidden: altura da ficha: 104px | base da foto: 102px | parágrafo de baixo em: 122px com display: flow-root: altura da ficha: 104px | base da foto: 102px | parágrafo de baixo em: 122px

Sem correção, o cartão fica com 26px de altura enquanto a foto termina em 102px: a foto escapa por baixo da borda e o parágrafo seguinte sobe por cima dela, começando em 43,59. Elemento flutuado não conta para a altura do pai.

overflow: hidden conserta a altura — e por isso virou remendo padrão por uma década. Só que ele conserta pelo motivo errado: cria um contexto de rolagem, e contexto de rolagem recorta o que passar da borda. O selo “urgente” posicionado 14px acima do cartão paga a conta:

html
<div class="folga"></div>
<article class="ficha">
  <span class="selo">urgente</span>
  <div class="foto"></div>
  <p class="nome">Thor, 4 anos</p>
</article>

A .folga é só um espaçador de 60px acima do cartão — ela existe para haver alguma coisa atrás do selo, e é assim que a medição descobre se o selo sumiu:

css
.folga { height: 60px; }
.ficha { position: relative; }
.selo { position: absolute; top: -14px; left: 20px; padding: 2px 8px; background: #333; color: #fff; }
js
const ficha = document.querySelector('.ficha').getBoundingClientRect();
const selo = document.querySelector('.selo').getBoundingClientRect();

// 10px acima da borda do cartão, bem onde o selo aparece
const alvo = document.elementFromPoint(selo.left + selo.width / 2, ficha.top - 10);

console.log('altura do cartão:', ficha.height.toFixed(0) + 'px');
console.log('10px acima da borda:', alvo.className);

Este trecho roda três vezes, uma em cada estado do cartão — sem correção nenhuma, com overflow: hidden e com display: flow-root:

sem nada: altura do cartão: 26px | 10px acima da borda: selo overflow: hidden: altura do cartão: 104px | 10px acima da borda: folga display: flow-root: altura do cartão: 104px | 10px acima da borda: selo

Com overflow: hidden, o ponto onde o selo deveria estar devolve .folga — o elemento que está atrás. O selo foi recortado fora. Com display: flow-root a altura é corrigida do mesmo jeito e o selo continua visível, porque nenhum contexto de rolagem foi criado.

flow-root diz exatamente isso: “por fora eu sou um bloco, por dentro eu abro uma raiz de fluxo nova”. Nada do lado de dentro vaza para fora, e nada é recortado. O que overflow: hidden fazia de efeito colateral, flow-root faz de propósito — sem os efeitos colaterais. Os outros usos de overflow continuam valendo e estão na lição de overflow.

O que treinar agora

Abra o DevTools numa página que você já fez, clique num elemento e olhe a linha display no painel de estilos computados. Depois faça o exercício das três perguntas em cada caixa: ela ocupa a linha inteira? aceita altura? o vizinho sabe que ela existe? Você acabou de ver a medição das três respostas.

O próximo passo da trilha de CSS é position — onde as caixas aprendem a sair do fluxo normal de vez, e onde o display que você escolheu aqui começa a decidir coisas que não são só tamanho.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • css
  • display
  • inline-block
  • fluxo
  • visibility

Perguntas frequentes

Qual a diferença entre display none e o atributo hidden do HTML?
O atributo hidden é atalho para display none — quem aplica é a folha de estilo do navegador. Por isso qualquer regra sua de display vence o atributo: um p[hidden] com display block volta a aparecer, e o hidden continua ali no HTML sem efeito nenhum.
Posso pôr display block num span e display inline numa div?
Pode. display muda só a caixa, nunca as regras de aninhamento do HTML — uma div com display inline continua proibida dentro de um parágrafo, e o navegador vai fechar o parágrafo antes dela na hora de montar o DOM.
display contents já pode ser usado em produção?
Em div e span genéricos, sim. Evite em elementos cuja semântica depende da caixa, como ul, ol e table: apagar a caixa deles ainda causa perda de papel na árvore de acessibilidade em alguns navegadores.
Ainda preciso do clearfix com ::after e clear both?
Não. display flow-root faz o mesmo trabalho numa linha, sem pseudo-elemento e sem os efeitos colaterais do overflow hidden. O clearfix só sobrevive em código legado que ninguém reescreveu.

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 — display — developer.mozilla.org
  2. MDN — visibility — developer.mozilla.org
  3. MDN — Flow layout — developer.mozilla.org
  4. CSS Display Module Level 3 — W3C — w3.org

Continue por aqui