FlowingDev

Font Fallbacks, explicados: desviando do terremoto de layout do CLS

Aprenda como as métricas de fonte do CSS, como size-adjust e ascent-override, previnem o Cumulative Layout Shift fazendo as fontes do sistema baterem perfeitamente com as suas web fonts.

Testar a ferramenta: Gerador de Fallback (CLS)

Em uma frase

As métricas de font fallback são um conjunto de superpoderes do CSS que esticam e espremem artificialmente uma fonte do sistema para que ela ocupe o mesmíssimo espaço matemático da sua web font customizada, evitando que o layout da página pule violentamente quando a fonte customizada finalmente carregar.

O problema que isso resolve

Para entender o problema, primeiro você precisa entender a guerra tipográfica mais antiga da web: o que a gente mostra pro usuário enquanto espera uma web font customizada de 150KB baixar numa conexão 3G capenga?

Historicamente, os browsers adotavam uma de duas abordagens, e ambas eram terríveis. A primeira era o FOIT (Flash of Invisible Text), onde o browser simplesmente escondia o texto até a fonte baixar. Os usuários ficavam encarando uma tela em branco, se perguntando se o seu site estava quebrado. A segunda era o FOUT (Flash of Unstyled Text), onde o browser renderizava imediatamente o texto usando uma fonte local do sistema (tipo Arial ou Times New Roman), e depois trocava às pressas pela sua web font maravilhosa no milissegundo em que ela chegava.

A comunidade de desenvolvimento web basicamente concordou que o FOUT é melhor para acessibilidade e performance percebida. Afinal, ler um texto feio é melhor do que não ler texto nenhum. Nós formalizamos isso com a regra CSS font-display: swap.

Mas o font-display: swap pariu um novo monstro: o Cumulative Layout Shift (CLS).

Toda fonte tem um esqueleto único. Um "x" na Roboto é fisicamente mais largo que um "x" na Arial. A altura das letras maiúsculas varia. O espaçamento embutido nos arquivos da fonte é completamente diferente. Quando o browser troca a Arial pela Roboto, o texto de repente passa a ocupar uma quantidade diferente de espaço horizontal e vertical. Parágrafos sofrem reflow. O texto quebra para uma nova linha.

Se isso acontece enquanto o usuário já está lendo ou prestes a clicar num botão, a página inteira pula como se um terremoto tivesse acabado de atingir o DOM. Botões fogem debaixo do cursor do usuário. Imagens são empurradas para baixo na página. O Google introduziu as métricas do Core Web Vitals para medir exatamente essa frustração, penalizando pesadamente o SEO de sites com um CLS alto. Nós precisávamos de um jeito de fazer a fonte feia e temporária do sistema imitar perfeitamente as dimensões físicas da fonte customizada bonitona.

Como funciona por debaixo dos panos

Para parar o layout shift, a gente não mexe na web font customizada. Em vez disso, criamos uma versão "Frankenstein" de uma fonte local do sistema usando a regra @font-face. Usamos descritores CSS especializados para sobrescrever (dar um override) a geometria interna da fonte do sistema.

A Anatomia do Box de uma Fonte

Para manipular uma fonte, você precisa entender como o browser calcula o box ao redor de uma linha de texto. Não é só uma questão de font-size. Isso depende de três métricas internas cravadas no próprio arquivo da fonte:

  1. Ascent: A distância da baseline invisível (onde as letras sentam) até o topo dos caracteres mais altos (tipo o topo de um 'h' ou 'T').
  2. Descent: A distância da baseline até a base dos caracteres que ficam pendurados mais para baixo (tipo a perninha de um 'y', 'g' ou 'p').
  3. Line Gap: O espaço vazio recomendado entre o descent de uma linha e o ascent da próxima linha (frequentemente chamado de entrelinha ou leading).

No CSS, a altura total de uma linha de texto é basicamente ascent + descent + line gap. Se a sua fonte customizada e a sua fonte do sistema tiverem valores diferentes para essas três métricas, seu layout vai pular verticalmente. Se as letras tiverem larguras diferentes, seu layout vai pular horizontalmente.

Entram em cena os Overrides

O CSS Fonts Module Level 4 introduziu quatro descritores mágicos que podem ser aplicados dentro de um bloco @font-face. Esses descritores interceptam a leitura que o browser faz das métricas internas do arquivo da fonte e as sobrescrevem na hora (on the fly).

  • size-adjust: Atua como um multiplicador para a fonte inteira. Se a sua fonte customizada for geralmente mais larga que a Arial, você pode colocar um size-adjust: 110% na Arial para esticá-la horizontalmente.
  • ascent-override: Sobrescreve a métrica de ascent interna da fonte.
  • descent-override: Sobrescreve a métrica de descent interna da fonte.
  • line-gap-override: Sobrescreve a métrica de line gap interna da fonte.

Juntando a Matemática

O truque é que o size-adjust escala tudo — incluindo as larguras horizontais, o ascent e o descent. Então, se você aumentar a escala de uma fonte do sistema em 110% para fazer a largura das linhas bater com a sua web font, você inadvertidamente deixou a altura vertical da fonte do sistema 110% mais alta, o que causa um layout shift vertical!

Para arrumar isso, os overrides (ascent-override, descent-override, line-gap-override) são calculados como porcentagens do valor do size-adjust, e não do tamanho original da fonte. Você usa o size-adjust para cravar perfeitamente a largura horizontal, e depois usa as métricas de override para dar aquela achatada na altura vertical de volta pra baixo (ou pra cima) para bater exatamente com a bounding box da sua fonte customizada.

Aqui está a cara do código resultante. Repare como criamos uma font family novinha e imaginária chamada Roboto-Fallback que envelopa a fonte local Arial com overrides matematicamente precisos:

/* 1. Load your actual web font */
@font-face {
  font-family: 'Roboto';
  src: url('/fonts/roboto.woff2') format('woff2');
  font-display: swap;
}

/* 2. Create the Frankenstein fallback */
@font-face {
  font-family: 'Roboto-Fallback';
  src: local('Arial');
  /* Stretch Arial horizontally to match Roboto */
  size-adjust: 104.5%; 
  /* Squish Arial's vertical metrics to match Roboto */
  ascent-override: 89.92%;
  descent-override: 23.44%;
  line-gap-override: 0%;
}

/* 3. Use them together in your CSS */
body {
  font-family: 'Roboto', 'Roboto-Fallback', sans-serif;
}

Quando a página carrega, o browser pinta instantaneamente a Roboto-Fallback. É só a Arial local, mas esticada na horizontal e achatada na vertical para bater com o footprint matemático exato da Roboto. Quando o arquivo real da Roboto termina de baixar, o swap acontece, as formas das letras mudam de design, mas o layout não move um único pixel.

Histórias do mundo real

A Armadilha do Botão "Cancelar"

Uma gigante do e-commerce estava sofrendo com abandono de carrinho e tickets furiosos no suporte. Os usuários reclamavam que estavam esvaziando seus carrinhos sem querer. A equipe de engenharia gravou as sessões dos usuários e encontrou o culpado. O site carregava uma fonte de marca pesada. O usuário via o botão "Finalizar Compra" bem do lado do botão "Esvaziar Carrinho". Bem na hora que eles clicavam com o mouse em "Finalizar Compra", a fonte da marca carregava. Essa fonte era significativamente mais larga que o padrão do sistema. O texto sofria reflow, empurrando o botão "Finalizar Compra" para a direita, e posicionando o botão "Esvaziar Carrinho" exatamente debaixo do clique certeiro do mouse do usuário. Implementar o size-adjust e os overrides de métrica travou os botões no lugar durante o swap da fonte, derrubando imediatamente a taxa de cancelamentos acidentais.

O Terremoto dos Artigos de Notícias

Um blog de tech super popular passou por um redesign gigantesco, com direito a uma fonte serifada moderna e linda pra leitura longa. Para priorizar a velocidade, eles usaram font-display: swap. Porém, leitores com conexões mais lentas passaram por um fenômeno bizarro: eles carregavam um artigo, desciam uns três parágrafos e começavam a ler. Dois segundos depois, a fonte serifada carregava. Como a fonte serifada tinha um ascent e descent maiores que o padrão do sistema, cada linha de texto crescia uma fração de milímetro. Ao longo de três parágrafos, isso virava uma bola de neve, criando um shift vertical gigantesco e jogando a frase que o usuário estava lendo completamente pra fora da tela. Adicionando um ascent-override e descent-override a uma fallback font local, o texto fez o swap perfeitamente no lugar, mantendo os olhos do leitor exatamente onde deveriam estar.

A Decepção do Score do Lighthouse

Um dev frontend passou três semanas otimizando uma aplicação web para o Core Web Vitals do Google. Comprimiu imagens pra WebP, colocou um defer nos JavaScripts não-críticos e minificou o CSS. Rodou o Google Lighthouse e esperava confiantemente por um score 100 redondinho. Em vez disso, tomou um 75. O relatório acusou uma penalidade pesada de Cumulative Layout Shift. Todo o shift era causado por um único título <H1> quebrando de uma linha pra duas quando uma web font customizada carregava. Em vez de redesenhar o header ou apelar pra textos invisíveis, o dev gerou métricas de fallback exatas para a fonte do título. O layout shift caiu pra zero, o score do Lighthouse bateu 100, e o dev finalmente conseguiu dormir.

Erros e armadilhas comuns

  • Chutar as porcentagens: Você não pode fazer esses números no olhômetro. Os cálculos exigem extrair os dados exatos de Unique Units Per Em (UPM), ascent, descent e line gap direto das tabelas binárias dentro dos arquivos tanto da web font quanto da fonte do sistema, e multiplicá-los cruzado. Use sempre um gerador programático para acertar a matemática.
  • Aplicar overrides na web font: Uma armadilha clássica é colocar o size-adjust direto na declaração @font-face da web font customizada. Isso não serve pra nada. Os overrides devem ser aplicados numa declaração @font-face completamente separada que carrega uma fonte local() do sistema, que você então coloca em segundo lugar na sua stack de font-family.
  • Escolher fallbacks estruturalmente opostos: As métricas de fonte têm seus limites. Se a sua fonte customizada for uma sans-serif ultra condensada, e você usar uma fonte serifada larga e cheia de floreios como sua base de fallback local, a matemática do size-adjust vai fazer a fonte serifada parecer completamente distorcida e ilegível enquanto espera pra fazer o swap. Escolha sempre uma fonte do sistema local que tenha uma estrutura esquelética mais ou menos parecida com a da sua web font.
  • Esquecer do line-gap-override: Devs costumam focar totalmente na largura e altura e presumem que deixar o line gap como normal ou simplesmente omitir tá de boa. Se o browser decidir adicionar um leading padrão para a fonte de fallback que não bata perfeitamente com o leading interno da web font, seu ritmo vertical vai sofrer shift. Sempre defina isso explicitamente.

Por que isso deve estar no seu radar

Você devia pensar em métricas de font fallback no exato momento em que decidir usar uma web font customizada em qualquer projeto onde a experiência do usuário e o SEO importam.

Historicamente, tipografia na web era um meio-termo entre estética (usar fontes bonitas) e performance (evitar layouts pulando). Com a chegada das métricas de fonte do CSS, esse meio-termo morreu. Sempre que você estiver auditando os Core Web Vitals de um site, brigando com um score de CLS alto, ou construindo componentes de UI reutilizáveis que não podem se dar ao luxo de tremer na tela, os overrides de font fallback são a solução de engenharia definitiva. Eles transformam as web fonts de um problema de carregamento para uma transição fluida e imperceptível.

Vá mais fundo

Teoria feita. Hora de pôr a mão na massa — 100% no seu navegador.

Testar a ferramenta: Gerador de Fallback (CLS)