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

Unidades no CSS: px, rem, em, %, vw e ch

Quando cada unidade muda de valor, por que em acumula dentro de listas aninhadas e qual usar em fonte, espaçamento e largura sem quebrar o zoom.

Rodolfo Mori13 min de leitura

Toda medida no CSS precisa de uma unidade, e a unidade decide de onde o navegador tira o número. px entrega um valor fixo, rem olha para a raiz do documento, em olha para o elemento pai, % muda de referência conforme a propriedade e vw/vh olham para a janela. Escolher errado não gera erro no console: gera um layout que quebra quando outra pessoa aumenta a fonte.

Os exemplos são sempre a mesma página: o painel de agendamento da Clínica Veterinária Pata Amiga. Todos os números deste artigo saíram de getComputedStyle e getBoundingClientRect rodando no Chromium 151.0.7922.34, controlado pelo Playwright. Se class, padding e width ainda soam novos, vale passar antes por o que é CSS e box model.

janela do navegador — vw, vh, svh, lvh, dvh html (raiz) — rem elemento pai / bloco contêiner — em, % o elemento — px é fixo; ch e ex leem a fonte daqui

px: o pixel do CSS não é o pixel da sua tela

px é a unidade absoluta do CSS. Absoluta quer dizer que ela não consulta nenhum ancestral: 200px é 200px em qualquer lugar do documento. O que ela não quer dizer é “200 pontinhos de luz do seu monitor”.

css
.selo-vacina {
  width: 200px;
  height: 120px;
}

Esse mesmo selo foi renderizado três vezes, mudando só a densidade da tela, e depois recortado num PNG para contar os pixels de verdade do arquivo:

devicePixelRatio 1: CSS 200px | PNG 200x120 pixels reais devicePixelRatio 2: CSS 200px | PNG 400x240 pixels reais devicePixelRatio 3: CSS 200px | PNG 600x360 pixels reais

A largura em CSS não mudou. O arquivo triplicou. Num celular moderno, um px do CSS costuma valer três pixels físicos — é por isso que a mesma página não fica minúscula numa tela de alta densidade.

O px do CSS é uma medida de referência: 1/96 de polegada, um ângulo de visão, não um ponto do hardware. Ele continua sendo a escolha certa para coisas que não devem crescer com o texto: border, box-shadow, o traço de 1px que separa duas linhas da agenda.

rem: o tamanho vem sempre da raiz, e só dela

rem significa root em: o tamanho da fonte do elemento <html>. Se a raiz vale 16px, 1rem vale 16px em qualquer profundidade do documento. Sempre.

Esta é a lista de especialidades da clínica, com quatro níveis de aninhamento:

html
<ul class="especialidades">
  <li>Clínica geral
    <ul><li>Dermatologia
      <ul><li>Alergia alimentar
        <ul><li>Dieta de eliminação</li></ul>
      </li></ul>
    </li></ul>
  </li>
</ul>
css
html { font-size: 16px; }
.especialidades li { font-size: 1.2rem; }

Medindo font-size de cada <li>, do primeiro ao quarto nível:

n1: 19.2px n2: 19.2px n3: 19.2px n4: 19.2px

Quatro níveis, um número só. 1.2rem é 1,2 × 16px, e o pai não entra na conta. Essa previsibilidade é o motivo de rem ser o padrão para font-size num projeto inteiro.

em: o tamanho vem do pai — e vai se acumulando

Agora troque uma letra. O mesmo HTML, o mesmo seletor, em no lugar de rem:

css
html { font-size: 16px; }
.especialidades li { font-size: 1.2em; }
n1: 19.2px n2: 23.04px n3: 27.648px n4: 33.1776px

O primeiro nível vale 19,2px porque o pai dele é o <ul class="especialidades">, que nenhuma regra tocou e por isso ficou com os 16px herdados da raiz. O segundo nível não usa 16px: usa 19,2px, o valor do pai dele. E multiplica de novo. No quarto nível, o texto está com 33px — mais que o dobro do que você pediu, sem que ninguém tenha escrito 33 em lugar nenhum.

É o efeito bola de neve. Ele só aparece quando a mesma propriedade font-size em em é aplicada a elementos que se aninham — lista dentro de lista, comentário dentro de comentário, cartão dentro de cartão.

Em outras propriedades, em olha para o próprio elemento

Aqui mora a confusão. Em font-size, em é o tamanho da fonte do pai. Em qualquer outra propriedade, em é o tamanho da fonte do próprio elemento, já calculado.

css
.card-pet { font-size: 20px; }
.card-pet .selo {
  font-size: 0.5em;
  padding: 1em;
  margin-top: 2em;
}
.card-pet font-size: 20px .selo font-size: 0.5em -> 10px .selo padding: 1em -> 10px .selo margin-top: 2em -> 20px

O font-size virou 10px porque leu os 20px do pai. O padding virou 10px porque leu os 10px que o próprio selo acabou de ganhar. Não é inconsistência: é a mesma regra, aplicada na ordem em que o navegador calcula as coisas.

E esse comportamento é exatamente o que torna em a melhor unidade para o espaçamento interno de um componente:

css
.botao {
  font-size: 1rem;
  padding: 0.75em 1.5em;
  border-radius: 0.5em;
}
.botao-grande { font-size: 1.5rem; }

O botão grande é o mesmo elemento com as duas classes (class="botao botao-grande"): ele herda o padding da primeira e só troca o font-size na segunda.

.botao -> font-size 16px | padding 12px 24px | border-radius 8px .botao-grande -> font-size 24px | padding 18px 36px | border-radius 12px

Uma linha mudou o tamanho da fonte e o botão inteiro cresceu proporcional: padding, cantos, tudo. Com padding: 12px fixo, o botão grande ficaria com o texto espremido. Regra prática: rem para o tamanho da fonte, em para o que gira em torno dela.

% muda de referência conforme a propriedade

Porcentagem é a unidade mais traiçoeira do CSS porque parece óbvia e não é. Ela sempre se refere a alguma coisa, mas o “alguma coisa” muda de propriedade para propriedade.

Esta é a ficha do paciente, com 600px de largura e 300px de altura:

css
.ficha { width: 600px; height: 300px; font-size: 20px; }

.ficha .foto    { width: 50%; }
.ficha .moldura { padding-top: 10%; padding-left: 10%; }
.ficha .nome    { font-size: 120%; line-height: 150%; }
.ficha .barra   { height: 50%; }
largura da .ficha -> 600px altura da .ficha -> 300px .foto width: 50% -> 300px .moldura padding-top: 10% -> 60px .moldura padding-left: 10% -> 60px .nome font-size: 120% -> 24px .nome line-height: 150% -> 36px .barra height: 50% -> 150px

Olhe para padding-top. A ficha tem 300px de altura, então 10% “deveria” dar 30px. Deu 60px: padding e margin em porcentagem se referem à largura do bloco contêiner, mesmo no topo e na base. É essa regra que faz o truque do padding-top: 56.25% para manter um vídeo em 16:9.

E height: 50% só funciona porque a ficha tem altura declarada. Com o pai em height: auto, a porcentagem não tem em que se apoiar. A mesma barra, dentro de dois pais que só diferem nisso:

css
.ficha-fixa { width: 600px; height: 300px; font-size: 20px; }
.ficha-auto { width: 600px;               font-size: 20px; }
.barra      { height: 50%; }
pai com height: 300px -> .barra height: 50% = 150px pai com height: auto -> .barra height: 50% = 23px

Os 23px são a altura do conteúdo — uma linha de texto de 20px. A declaração height: 50% foi simplesmente descartada.

line-height: 1.5 e line-height: 150% não são a mesma coisa

Com porcentagem, o navegador calcula o valor uma vez, no elemento onde você escreveu, e transmite esse resultado em pixels para os filhos. Sem unidade, ele transmite o número e cada filho faz a própria conta.

css
.bloco { font-size: 16px; }
.bloco.numero     { line-height: 1.5;  }
.bloco.percentual { line-height: 150%; }
.bloco .titulo    { font-size: 40px; }
pai line-height: 1.5 -> 24px filho (font-size: 40px) -> 60px pai line-height: 150% -> 24px filho (font-size: 40px) -> 24px

O título de 40px herdou uma entrelinha de 24px e ficou com as letras se tocando. Por isso line-height sem unidade é a recomendação padrão.

vw, vh e a barra do celular: svh, lvh e dvh

vw e vh são 1% da largura e da altura da janela. Elas ignoram completamente o elemento pai — e é aí que quebram.

css
.coluna    { width: 400px; }
.banner-pc { width: 100%;  }
.banner-vw { width: 100vw; }

.capa-vh  { height: 100vh;  }
.capa-svh { height: 100svh; }
.capa-lvh { height: 100lvh; }
.capa-dvh { height: 100dvh; }
desktop 800x600 .banner-pc (100%) 400px .banner-vw (100vw) 800px 100vh 600px 100svh 600px 100lvh 600px 100dvh 600px celular 390x664 .banner-pc (100%) 400px .banner-vw (100vw) 390px 100vh 664px 100svh 664px 100lvh 664px 100dvh 664px

Dentro de uma coluna de 400px, 100% deu 400px e 100vw deu 800px — o dobro da coluna, transbordando para fora dela. No viewport de celular, 100vw caiu para 390px enquanto 100% continuou nos 400px do pai. São referências diferentes, e misturar as duas é a causa mais comum de rolagem horizontal indesejada.

Repare que 100vh, 100svh, 100lvh e 100dvh deram o mesmo número nas duas medições. Isso acontece porque o navegador de teste não tem barra de endereço que recolhe. Num celular de verdade elas se separam:

unidade mede a altura quando usar
vh a altura “grande”, como lvh na maioria dos navegadores evite em tela cheia no celular
svh com todas as barras visíveis (a menor) garante que nada fique escondido
lvh com as barras recolhidas (a maior) fundo que pode ser cortado
dvh a altura de agora, muda durante a rolagem herói de tela cheia

Para a capa de tela cheia do agendamento, min-height: 100dvh resolve o clássico “o botão fica escondido atrás da barra do navegador”.

ch e ex: medir em caracteres em vez de em pontos

1ch é a largura do caractere 0 na fonte em uso; 1ex é a altura da letra x. As duas mudam quando a fonte muda:

css
.medida   { display: inline-block; width: 1ch; }
.medida.x { width: 1ex; }

.sistema { font-family: system-ui, sans-serif; }
.georgia { font-family: Georgia, serif; }
.mono    { font-family: "Courier New", monospace; }

A mesma .medida de 1ch, com font-size: 16px, medida nas três fontes:

system-ui 1ch -> 10.078125px Georgia 1ch -> 9.8125px Courier New 1ch -> 9.59375px system-ui 1ex -> 8.421875px Georgia 1ex -> 7.703125px Courier New 1ex -> 6.765625px

O uso clássico é limitar a largura de um texto corrido: max-width: 60ch para o comunicado que a clínica manda no fim da consulta. Mas cuidado com a promessa — 60ch não é “60 caracteres”, exceto em fonte monoespaçada.

A frase do comunicado tem exatamente 60 caracteres — “Thor precisa ficar em jejum de oito horas antes da cirurgia.” — e foi medida contra uma caixa de 60ch, nas mesmas duas fontes:

css
.caixa { width: 60ch; }
a frase tem 60 caracteres system-ui -> frase 424.390625px | caixa 60ch = 604.6875px Courier New -> frase 576.09375px | caixa 60ch = 576.09375px

Em Courier New os dois números batem exatamente: monoespaçada, todo caractere tem a mesma largura. Em system-ui, a frase ocupa 424px numa caixa de 605px — sobra espaço para umas 25 letras a mais, porque i, l e espaço são bem mais estreitos que o 0. 60ch continua sendo um bom limite de leitura; só não prometa contagem exata.

O erro que mais aparece: número sem unidade

Este é o erro que trava iniciante em CSS, e ele é silencioso. Não existe SyntaxError, não existe aviso no console: o navegador simplesmente descarta a declaração inválida e segue com o valor anterior.

css
.painel { width: 600px; }

.painel .sem-unidade { width: 300; }
.painel .com-espaco  { width: 300 px; }
.painel .calc-errado { width: calc(100%-20px); }
.painel .calc-certo  { width: calc(100% - 20px); }
.painel .zero        { width: 0; }
width: 300 -> 600px width: 300 px -> 600px width: calc(100%-20px) -> 600px width: calc(100% - 20px)-> 580px width: 0 -> 0px

As três primeiras caixas ficaram com 600px, a largura total do painel, porque width voltou a valer auto. Você olha para o CSS, lê width: 300, e jura que está lá — mas o navegador nunca aceitou aquela linha.

Três detalhes que geram esse resultado:

  • Sem unidade não vale. 300 não é comprimento.
  • Espaço entre número e unidade não vale. 300 px são dois valores.
  • calc() exige espaço em volta de - e +. Sem espaço, 100%-20px é lido como um único valor esquisito e a expressão inteira é rejeitada.

Dá para confirmar isso na hora, no console do navegador, sem inspecionar elemento nenhum:

js
CSS.supports('width', '300');
CSS.supports('width', '300px');
CSS.supports('width', 'calc(100%-20px)');
CSS.supports('line-height', '1.5');
CSS.supports("width", "300") -> false CSS.supports("width", "300px") -> true CSS.supports("width", "calc(100%-20px)") -> false CSS.supports("line-height", "1.5") -> true

As duas exceções que confundem: 0 pode ir sem unidade (zero é zero em qualquer escala) e line-height aceita número puro de propósito, como você viu acima.

Fonte em px ignora quem aumentou a letra do navegador

Chegou a parte que decide o projeto. Nas configurações do navegador existem duas formas de aumentar o texto, e elas não são a mesma coisa. O zoom de página amplia tudo junto, inclusive o que está em px — por isso ele nunca “quebra”. A outra é a preferência de tamanho de fonte, que só mexe no que é relativo. Muita gente com baixa visão deixa essa preferência em 20px ou 24px e nunca mais toca no zoom, porque zoom desalinha o layout inteiro.

Este é o cartão de aviso da clínica, escrito duas vezes: uma em px, outra em rem.

css
.cartao-px  { font-size: 14px;     padding: 16px; width: 320px; }
.cartao-rem { font-size: 0.875rem; padding: 1rem; width: 20rem; }

A mesma página, aberta com a preferência de fonte em 16px e depois em 24px:

preferencia do navegador: 16px (html = 16px) .cartao-px -> 14px | padding 16px | largura 352px .cartao-rem -> 14px | padding 16px | largura 352px preferencia do navegador: 24px (html = 24px) .cartao-px -> 14px | padding 16px | largura 352px .cartao-rem -> 21px | padding 24px | largura 528px

Com a preferência em 16px, os dois cartões são idênticos — 352px, 14px de texto. Com a preferência em 24px, o cartão em rem acompanhou: texto de 21px, respiro de 24px, cartão de 528px. O cartão em px ficou exatamente igual: 14px de texto para quem pediu letra maior.

Não aparece bug nenhum no seu monitor. Aparece na tela de quem precisa.

O truque do 62.5% ainda é seguro. Já 10px não é

Muita gente escreve html { font-size: 62.5% } para que 1rem vire 10px e a conta mental fique fácil. Outra gente escreve html { font-size: 10px } achando que é a mesma coisa. Não é:

css
html { font-size: 62.5%; }  /* versão A */
html { font-size: 10px;  }  /* versão B */
.aviso { font-size: 1.4rem; }
preferencia 16px | html { font-size: 62.5% } -> raiz 10px | .aviso (1.4rem) 14px preferencia 16px | html { font-size: 10px } -> raiz 10px | .aviso (1.4rem) 14px preferencia 24px | html { font-size: 62.5% } -> raiz 15px | .aviso (1.4rem) 21px preferencia 24px | html { font-size: 10px } -> raiz 10px | .aviso (1.4rem) 14px

Na preferência padrão, as duas versões dão o mesmo resultado. Quando a pessoa sobe para 24px, a versão A escala junto (raiz de 15px, aviso de 21px) e a versão B congela. 62.5% é relativo; 10px é uma trava.

em na media query ignora o font-size da raiz

Um detalhe que pega gente experiente. Dentro de @media, em e rem usam o tamanho inicial da fonte, e não o que você declarou em html:

css
html { font-size: 20px; }
@media (min-width: 40em) { .agenda { background: green; } }
viewport 639px | html 20px | (min-width: 40em) -> false viewport 640px | html 20px | (min-width: 40em) -> true viewport 799px | html 20px | (min-width: 40em) -> true viewport 800px | html 20px | (min-width: 40em) -> true

A quebra aconteceu em 640px, que é 40 × 16, e não em 800px, que seria 40 × 20. Isso é bom: sua grade não se move quando alguém mexe na fonte. Só não conte com o font-size da raiz para calcular breakpoint. Se isso ainda é novidade, a lição de media queries entra em detalhe.

Tabela de decisão: qual unidade para cada propriedade

Este é o critério que eu uso e defendo, com o motivo de cada escolha:

propriedade unidade por quê
font-size de texto rem acompanha a preferência da pessoa e não acumula
font-size de título fluido clamp() com rem e vw cresce com a tela sem travar o zoom
padding e gap de componente em escala junto com a fonte daquele componente
margin entre blocos rem espaçamento da página não depende do componente
width de coluna de texto ch o limite real de leitura é em caracteres
max-width de contêiner rem trabalha junto com os breakpoints
width de bloco dentro do pai % segue o contêiner, não a janela
border, box-shadow, outline px detalhe visual que não precisa crescer
altura de tela cheia no celular dvh acompanha a barra que recolhe
breakpoint em @media em ou px os dois usam a base de 16px

Duas coisas que não entram nessa lista: pt, cm e in, que só fazem sentido em folha de estilo para impressão, e px em font-size de texto corrido, que é o assunto da seção anterior.

O que vem depois

Com as unidades resolvidas, o próximo salto é parar de repetir número no arquivo: variáveis CSS guardam a escala de espaçamento em um lugar só, e tipografia monta a escala de tamanhos com clamp(). O mapa completo da matéria está no guia de CSS e a ordem de estudo, na 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
  • unidades
  • rem
  • em
  • viewport
  • acessibilidade

Perguntas frequentes

Posso usar px para tudo e resolver com media query?
Media query reage à largura da tela, e mudar a fonte padrão do navegador não muda largura nenhuma. A condição continua dando o mesmo resultado de antes, e quem pediu letra maior segue vendo o texto de 14px.
rem e em dão o mesmo resultado quando a raiz vale 16px?
Dão, enquanto nenhum ancestral mudar de tamanho. A diferença aparece no primeiro elemento aninhado dentro de outro que já tinha font-size próprio, e a partir dali o em multiplica.
Qual a diferença entre 100vh e 100%?
100vh mede a altura da janela; 100% mede a altura do bloco que contém o elemento. Se esse bloco não tiver altura definida, a porcentagem é ignorada e o elemento volta para height auto.
Existe unidade certa para border e box-shadow?
px, na maioria dos casos. Contorno e sombra são detalhes visuais que não precisam crescer junto com o texto, e em rem uma borda de 1px vira 1,5px quando alguém aumenta a fonte padrão.

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

Fontes consultadas

  1. MDN — CSS values and units — developer.mozilla.org
  2. CSS Values and Units Module Level 4 — w3.org
  3. WCAG 2.2 — Resize Text (1.4.4) — w3.org

Continue por aqui