clamp() é interpolação linear disfarçada
A especificação CSS Values and Units Module Level 4 define `clamp(MIN, VAL, MAX)` como exatamente equivalente a `max(MIN, min(VAL, MAX))`. Leia da direita para a esquerda: o `min(VAL, MAX)` impede que o valor passe do teto; o `max(MIN, ...)` impede que caia abaixo do piso. No meio fica `VAL`, o "valor preferido", e é ali que mora o comportamento fluido. Se `VAL` fosse constante, `clamp()` não teria graça nenhuma; a curva só ganha vida porque `VAL` costuma depender da largura da tela, via unidades de viewport.
- MIN (piso)
- O menor valor permitido. Em telas estreitas, é ele que vence.
- VAL (preferido)
- A expressão fluida, a reta que interpola entre os limites. É onde entra o `vw`.
- MAX (teto)
- O maior valor permitido. Em telas largas, é ele que vence.
Repare que `clamp()` não faz nenhuma suavização, curva ou easing: entre o piso e o teto o crescimento é estritamente linear. Se você quer uma progressão com aceleração, texto que cresce devagar e depois dispara, `clamp()` sozinho não entrega; aí o terreno é o de curvas de tempo, que você molda no editor de cubic-bezier. Para tipografia responsiva, porém, a reta é justamente o que se quer: previsível e fácil de derivar.
Derivando a fórmula do zero
O problema é: quero um tamanho mínimo `y1` numa largura `x1` e um tamanho máximo `y2` numa largura maior `x2`, com uma reta ligando os dois pontos. Isso é geometria de ensino médio. A inclinação da reta é a variação vertical dividida pela horizontal; a intersecção é onde ela cruza o eixo em `x = 0`. A única sutileza de CSS é traduzir a reta para unidades que o navegador entende: a parte proporcional à largura vira `vw` (1vw = 1% da largura da janela), e a parte fixa vira `rem`.
m = (y₂ − y₁) / (x₂ − x₁) [inclinação / slope]
b = y₁ − m · x₁ [intersecção em px / intercept in px]
font-size = clamp( y₁ , b_rem + (100·m)vw , y₂ )- y₁
- tamanho de fonte mínimo (o piso do clamp)
- y₂
- tamanho de fonte máximo (o teto do clamp)
- x₁
- largura de viewport onde a fonte deve ficar em y₁
- x₂
- largura de viewport onde a fonte deve atingir y₂
- m
- inclinação da reta, em px de fonte por px de largura
- b
- intersecção da reta em x = 0, em px
- b_rem
- a intersecção b convertida de px para rem (b ÷ tamanho raiz)
- Escolha os quatro pontosDefina `y1` e `x1` (o par mínimo) e `y2` e `x2` (o par máximo). Trabalhe tudo em px para a conta.
- Calcule a inclinação`m = (y2 − y1) / (x2 − x1)`. Esse número é minúsculo, dezenas de milésimos, porque é px de fonte por px de largura.
- Calcule a intersecção`b = y1 − m · x1`. É o valor da reta em `x = 0`; pode ser positivo ou negativo.
- Traduza para CSSPreferido = `b_rem + (100·m)vw`. Monte `clamp(y1, preferido, y2)` com y1 e y2 em rem.
Dois exemplos com os números
Exemplo 1, corpo de texto. Quero 1rem (16px, com raiz de 16px) numa janela de 320px, subindo até 2rem (32px) numa janela de 1280px. Então `y1 = 16`, `x1 = 320`, `y2 = 32`, `x2 = 1280`. A inclinação é `m = (32 − 16) / (1280 − 320) = 16 / 960 = 0,016667`, que em vw é `100 × 0,016667 = 1,6667vw`. A intersecção é `b = 16 − 0,016667 × 320 = 16 − 5,3333 = 10,6667px`, que em rem é `10,6667 ÷ 16 = 0,6667rem`. Resultado: `clamp(1rem, 0.6667rem + 1.6667vw, 2rem)`.
Confira: numa janela de 320px, `1,6667vw = 5,3333px` e somando `0,6667rem = 10,6667px` dá exatamente 16px = 1rem. Numa janela de 1280px, `1,6667vw = 21,333px` mais 10,6667px dá 32px = 2rem. A reta passa pelos dois pontos que pedimos, e o `clamp()` congela nos limites fora do intervalo.
Exemplo 2, um título. Quero 1,5rem (24px) numa janela de 360px e 3rem (48px) numa janela de 1440px. Então `m = (48 − 24) / (1440 − 360) = 24 / 1080 = 0,022222`, ou seja `2,2222vw`. A intersecção é `b = 24 − 0,022222 × 360 = 24 − 8 = 16px`, que em rem é exatamente `16 ÷ 16 = 1rem`, um caso em que a conta sai redonda. Resultado: `clamp(1.5rem, 1rem + 2.2222vw, 3rem)`. Em 360px: `2,2222vw = 8px` mais 1rem (16px) = 24px = 1,5rem; em 1440px: `2,2222vw = 32px` mais 16px = 48px = 3rem.
:root {
/* Example 1, body text: 16px (1rem) @ 320px -> 32px (2rem) @ 1280px
m = (32-16)/(1280-320) = 1/60 px/px -> 100m = 1.6667vw
b = 16 - (1/60)*320 = 10.6667px -> 10.6667/16 = 0.6667rem */
--fs-body: clamp(1rem, 0.6667rem + 1.6667vw, 2rem);
/* Example 2, heading: 24px (1.5rem) @ 360px -> 48px (3rem) @ 1440px
m = (48-24)/(1440-360) = 1/45 px/px -> 100m = 2.2222vw
b = 24 - (1/45)*360 = 16px -> 16/16 = 1rem */
--fs-h1: clamp(1.5rem, 1rem + 2.2222vw, 3rem);
}
body { font-size: var(--fs-body); }
h1 { font-size: var(--fs-h1); }A curva: platô, rampa, platô
Desenhar o tamanho da fonte em função da largura torna óbvio o que `clamp()` faz: um platô no mínimo enquanto a tela é estreita, uma rampa linear no intervalo escolhido e um platô no máximo quando a tela fica larga demais. O gráfico abaixo compara dois clamps. O conservador é o Exemplo 1 (16px→32px, entre 320px e 1280px). O agressivo é `clamp(1.5rem, 0.1207rem + 6.8966vw, 4rem)` (24px→64px, entre 320px e 900px): rampa muito mais íngreme, que satura cedo. Repare que ambos são retas entre dois joelhos, não há suavização.
Ver os dados
| x | Conservador (16→32px) | Agressivo (24→64px) |
|---|---|---|
| 280 | 16px | 24px |
| 320 | 16px | 24px |
| 480 | 18,7px | 35px |
| 640 | 21,3px | 46,1px |
| 800 | 24px | 57,1px |
| 960 | 26,7px | 64px |
| 1.280 | 32px | 64px |
| 1.600 | 32px | 64px |
Um clamp agressivo demais tem dois problemas: a fonte muda rápido demais entre dois tamanhos de tela próximos (desconfortável ao redimensionar) e ele satura cedo, desperdiçando a faixa de larguras maiores. Um clamp conservador respira melhor. Em vez de decorar os números, gere o seu e veja a curva na ferramenta abaixo.
A armadilha da WCAG 1.4.4
Aqui está a parte que os tutoriais de "fonte que cresce" costumam pular. O critério WCAG 1.4.4 (Resize Text), nível AA, exige que o texto possa ser redimensionado até 200% sem perda de conteúdo ou funcionalidade, sem tecnologia assistiva, só com os controles do próprio navegador. E `font-size` em `vw` puro falha exatamente esse teste. Unidades de viewport são ancoradas ao tamanho da janela, não à preferência de fonte do usuário; quando alguém dá zoom de texto ou aumenta a fonte-base do navegador, o `vw` não se move. O usuário aperta "aumentar" e nada acontece.
Há uma segunda salvaguarda importante: não fixe um teto (`MAX`) baixo demais. Se o máximo do seu `clamp()` for menor que o dobro do tamanho-base, você impede o usuário de chegar aos 200% que a norma pede, mesmo com o termo em rem. Uma regra prática segura: só use um máximo se ele for pelo menos o dobro do mínimo, ou aceite não ter máximo nenhum. E teste de verdade, o critério exige verificação manual, redimensionando o texto e confirmando que ele cresce.
- 2016Nascem os "CSS Locks"
Mike Riethmuller populariza a fórmula de interpolação linear com calc() e vw; Tim Brown a apelida de CSS Locks. É a mesma reta que o clamp() embute hoje.
- 2019Roselli documenta a falha de zoom
Adrian Roselli publica "Responsive Type and Zoom", mostrando com testes que texto ancorado em vw impede o usuário de ampliar a fonte.
- 2020clamp() chega aos navegadores
clamp(), min() e max() passam a ter suporte cruzado (Chrome 79, Firefox 75, Safari 13.1), tornando o CSS lock uma função nativa de uma linha.
- 2022Unidades svw / lvw / dvw
As unidades de viewport pequena, grande e dinâmica chegam aos navegadores (Safari 15.4, Firefox 101, Chrome 108) para lidar com a barra de endereço móvel.
Sobre essas unidades novas: `vw` é igual a `lvw` (viewport grande, com a barra de endereço recolhida). `svw` mede a viewport pequena (barra visível) e `dvw` acompanha a mudança em tempo real. Elas existem porque, no celular, a barra de endereço aparece e some ao rolar, mudando a largura útil. Para `font-size`, isso raramente importa, a diferença é mínima na horizontal, e `dvw` pode até causar reflow ao rolar; o cuidado com essas unidades pesa mais em alturas (`dvh`) e em elementos que ocupam a tela inteira.
Além da tipografia (e a revisão)
O mesmo `clamp()` serve para qualquer propriedade que aceite comprimento: espaçamentos internos, gaps de grid e larguras de coluna que respiram com a tela sem uma pilha de media queries. Mas há detalhes que só aparecem quando você sai do `font-size`. O acordeão reúne os três que mais geram bug.
clamp() em padding, gap e largura de coluna
Para espaçamento, o padrão é `padding: clamp(1rem, 5vw, 3rem)`, margens que crescem com a tela mas nunca colam nas bordas nem explodem. Para larguras de conteúdo, `width: clamp(45ch, 60vw, 75ch)` mantém a linha de texto numa faixa legível (o `ch` é a largura do caractere "0"). Aqui a componente em rem é opcional: espaçamento e largura não estão sujeitos ao critério 1.4.4, que fala de texto. Mesmo assim, misturar uma unidade relativa à fonte (rem/em/ch) costuma dar resultados mais previsíveis.
Por que min() e max() sozinhos raramente bastam
`min(a, b)` retorna o menor dos valores e `max(a, b)` o maior. Um `width: min(90vw, 60rem)` limita a largura a 60rem em telas grandes, sem piso; um `max()` garante um mínimo, sem teto. O que quase todo layout responsivo quer, porém, é as duas coisas ao mesmo tempo: um piso e um teto com uma preferência fluida no meio. Fazer isso com min() e max() aninhados é escrever `max(MIN, min(VAL, MAX))`, que é literalmente a definição de clamp(). Ou seja: você pode, mas clamp() é o atalho legível para o caso comum.
A armadilha de inverter MIN e MAX
Se você trocar os limites, `clamp(MAX, VAL, MIN)` com o maior primeiro, o CSS não corrige por você. A especificação resolve `max(MIN, min(VAL, MAX))` ao pé da letra: quando `MIN > MAX`, o `min()` interno devolve MAX e o `max()` externo devolve MIN, então o resultado trava em MIN e ignora tudo. O texto fica preso num tamanho e você jura que o clamp "não funciona". Regra: o primeiro argumento é sempre o menor, o terceiro sempre o maior.
Antes de mandar para produção, passe pela revisão abaixo. Ela cobre os erros que não aparecem no seu monitor de 1440px mas explodem no celular de alguém ou no zoom de quem precisa dele.
- O valor preferido inclui um termo em rem (a intersecção b_rem), nunca vw puro no font-size.
- Testei com zoom em 200% e o texto de fato cresceu (WCAG 1.4.4).
- Testei numa janela de 320px de largura e a fonte não ficou minúscula nem estourou o layout.
- O máximo (MAX) é pelo menos o dobro do mínimo, ou não há máximo, para não travar o zoom antes dos 200%.
- MIN é o primeiro argumento e MAX o último; conferi que não inverti.
- Não usei clamp() cru em line-height sem cuidado: line-height sem unidade escala com a fonte; um clamp em px/rem congela o espaçamento entre linhas.
Perguntas frequentes
Como o clamp() funciona no CSS?
Por que não devo usar só vw no font-size?
Como calcular os valores de um clamp() para fonte?
Qual a diferença entre vw, svw, lvw e dvw?
Posso usar clamp() em padding e largura, não só em fonte?
`clamp()` é uma reta com dois freios: derive a inclinação e a intersecção a partir dos seus pares mínimo e máximo, e nunca escreva o valor preferido só em vw. O termo em rem não é enfeite, é o que mantém o texto obediente ao zoom e ao critério WCAG 1.4.4. Faça a conta uma vez, teste em 200% e a 320px, e você tem tipografia fluida que também é acessível.