Uma linha de texto com tamanhos mistos pode ultrapassar os valores de line-height de ambos os intervalos porque o alinhamento pela linha de base combina suas extensões superior e inferior. Este guia mostra como diagnosticar essa altura extra e ajustar uma linha com dois rótulos em um navegador para desktop no Mac.

Por que o intervalo mais alto não determina a altura da linha?

Considere um cabeçalho abstrato de página que contém um Label A grande ao lado de um Label B pequeno. Ambos os intervalos têm line-height: 40px, mas a linha que os contém mede mais de 40 pixels CSS. Esse resultado decorre da forma como o layout em linha alinha as caixas antes de calcular a linha que as envolve.

Para texto em linha comum, o navegador calcula a contribuição de altura de cada caixa, alinha as caixas verticalmente e, em seguida, envolve as extensões combinadas. Com o alinhamento pela linha de base, os pontos mais altos e mais baixos podem pertencer a intervalos diferentes. Assim, o resultado pode ultrapassar a contribuição individual de qualquer um deles. O elemento pai também contribui com uma caixa invisível de tamanho mínimo, tradicionalmente chamada de strut. W3C: Cálculos da altura da linha

A linha de base é a referência de alinhamento do texto. Ela não é o ponto médio vertical da contribuição da altura de linha. Dar alturas de linha iguais a textos de tamanhos diferentes não faz com que ambos tenham distâncias iguais acima e abaixo dessa linha de base. O valor inicial de vertical-align é baseline. MDN: vertical-align

Mantenha separadas três medidas: as métricas de ascendente e descendente da fonte, os limites visíveis das letras específicas e o espaço contribuído para o layout da linha. O fundo colorido ou o retângulo de seleção de um intervalo não deve ser tratado como uma medição direta da caixa de linha que o envolve; o CSS distingue a geometria de pintura em linha dos cálculos de altura de linha. W3C: Entrelinha e meia entrelinha

Como duas contribuições de 40px podem produzir uma linha de 48,4px?

Para texto comum alinhado pela linha de base com uma altura de linha explícita, o cálculo útil é: subtraia a soma do ascendente e do descendente da fonte da altura de linha e distribua igualmente o espaço restante acima e abaixo. Esse espaço extra é chamado de entrelinha; cada metade é uma meia entrelinha. O Working Draft do CSS Inline Layout Level 3 descreve esse cálculo e permite uma entrelinha negativa. O status de rascunho não comprova o suporte dos navegadores a todas as propriedades mais recentes que ele contém. W3C: Cálculo dos limites do layout

Os valores a seguir são ilustrativos, não medições de Arial nem de qualquer fonte para Mac. Suponha uma strut do elemento pai cuja contribuição corresponda à do intervalo pequeno, sem outro conteúdo em linha ou deslocamentos verticais.

Contribuição Ascendente da fonte Descendente da fonte Altura de linha Meia entrelinha Acima da linha de base Abaixo da linha de base
Intervalo grande 32px 8px 40px 0px 32px 8px
Intervalo pequeno 9,6px 2,4px 40px 14px 23,6px 16,4px
Strut do elemento pai 9,6px 2,4px 40px 14px 23,6px 16,4px

Neste exemplo com alinhamento pela linha de base, considere separadamente a maior extensão de cada lado: max(32, 23.6, 23.6) + max(8, 16.4, 16.4) = 48.4px. Cada contribuição totaliza 40px, mas o intervalo grande define o topo, enquanto o intervalo pequeno e o elemento pai definem a parte inferior. Esse resultado demonstrado deriva do cálculo de layout documentado, não de uma medição prevista do navegador. W3C: Modelo de layout em linha

A pergunta de diagnóstico, portanto, é: qual participante define cada borda? Reduzir a altura de linha do intervalo pequeno pode remover sua contribuição inferior, mas deixar intacta uma contribuição idêntica da strut do elemento pai.

Como reproduzir e inspecionar isso em um Mac?

Use o Chrome no seu Mac para este passo a passo. Abra uma nova aba, navegue até about:blank e pressione Command + Option + J para abrir o Console. O Chrome documenta esse atalho para executar JavaScript. Chrome DevTools: visão geral

Execute o programa completo abaixo nessa aba em branco. Ele cria três linhas sintéticas com os mesmos rótulos, imprime as medições e usa uma raiz shadow para manter juntos os estilos da demonstração. Não é necessário instalar pacotes nem usar bibliotecas externas. Executá-lo novamente substitui a demonstração anterior.

(async () => {
  document.getElementById('line-box-demo')?.remove();
  const host = document.createElement('div');
  host.id = 'line-box-demo';
  document.body.append(host);
  const root = host.attachShadow({ mode: 'open' });
  const style = document.createElement('style');
  style.textContent = `
    :host { display: block; padding: 24px; color: #222; background: #fff; }
    .label { margin: 20px 0 6px; font: 14px Arial, sans-serif; }
    .row { font: 12px/40px Arial, sans-serif; white-space: nowrap;
      margin: 0; padding: 0; border: 0; background: #e8eef4; }
    .large { font-size: 40px; line-height: 40px; }
    .small { font-size: 12px; line-height: 40px; }
    .trim-span .small, .trim-both .small { line-height: 12px; }
    .trim-both { font-size: 40px; }
  `;
  root.append(style);
  const cases = [
    ['', 'Shared 40px line-height'],
    ['trim-span', 'Smaller span only'],
    ['trim-both', 'Parent and smaller span']
  ];
  const rows = cases.map(([className, label]) => {
    const caption = document.createElement('p');
    caption.className = 'label';
    caption.textContent = label;
    const row = document.createElement('div');
    row.className = `row ${className}`;
    for (const [size, text] of [['large', 'Label A'], ['small', ' Label B']]) {
      const span = document.createElement('span');
      span.className = size;
      span.textContent = text;
      row.append(span);
    }
    root.append(caption, row);
    return { label, row };
  });
  await document.fonts.ready;
  console.table(rows.map(({ label, row }) => ({
    case: label,
    parentLineHeight: getComputedStyle(row).lineHeight,
    largeLineHeight: getComputedStyle(row.children[0]).lineHeight,
    smallLineHeight: getComputedStyle(row.children[1]).lineHeight,
    rowHeight: row.getBoundingClientRect().height
  })));
})();

O programa aguarda document.fonts.ready, que é resolvida depois que o carregamento das fontes do documento e o trabalho de layout relacionado são concluídos. Se você introduzir outra fonte ou alterar o conteúdo depois, faça novas medições quando essas alterações tiverem sido aplicadas. MDN: FontFaceSet.ready

Leia as três linhas como uma comparação controlada. Na primeira, os dois intervalos e o elemento pai têm altura de linha de 40px. Na segunda, somente a altura de linha do intervalo pequeno é reduzida para 12px. Na terceira, o tamanho da fonte do elemento pai também é alterado para 40px, fazendo com que sua strut corresponda ao tamanho da fonte e à altura de linha do texto grande. Se a segunda linha continuar alta, mas a terceira diminuir, essa comparação identifica a contribuição do elemento pai.

Use os resultados do seu navegador em vez de esperar o valor ilustrativo de 48,4px. O programa mede cada linha com getBoundingClientRect(), que retorna o retângulo delimitador de um elemento, incluindo preenchimento e bordas. Aqui, as linhas não têm nenhum dos dois, contêm uma única linha sem quebra e têm altura automática, o que torna suas alturas aproximações úteis das alturas das caixas de linha. Medir um intervalo com a mesma API mediria o retângulo desse intervalo, sem expor diretamente a caixa de linha que o envolve. MDN: getBoundingClientRect()

O que você deve verificar em um layout existente?

Clique no texto relevante com o botão de controle pressionado e escolha Inspecionar. Em Elements, inspecione os dois intervalos e o bloco que os contém. Use Styles para rastrear declarações e Computed para ver os valores aplicados; ative Show All quando necessário para inspecionar propriedades herdadas. O Chrome documenta essas visualizações e os filtros de propriedades. Chrome DevTools: referência de recursos CSS

Registre font-family, font-size, line-height, display e vertical-align para cada participante. Comece pelo elemento pai em vez de olhar apenas para os dois intervalos. Em seguida, use esta sequência para isolar a causa:

  1. Confirme o modelo de layout. Verifique se os elementos filhos participam do fluxo em linha normal ou se são itens flex ou grid.
  2. Confirme que há uma única linha. Use uma área de teste larga e rótulos sintéticos curtos para que as quebras de linha não confundam a medição.
  3. Verifique o alinhamento. Remova temporariamente os deslocamentos da linha de base ou o alinhamento vertical fora do padrão antes de aplicar o cálculo simples acima.
  4. Altere um contribuinte por vez. Reduza a altura de linha do intervalo pequeno, faça a medição e depois teste separadamente a contribuição da fonte e da altura de linha do elemento pai.
  5. Restaure as condições reais. Depois de identificar a causa, verifique novamente a fonte pretendida, a largura disponível e o texto.

Verifique também a herança. Um line-height: 1.2 sem unidade transmite um multiplicador que cada descendente aplica ao próprio tamanho de fonte. Um valor de comprimento, como 40px, transmite um comprimento fixo, a menos que seja sobrescrito; as porcentagens são calculadas em relação ao tamanho da fonte do elemento que as declara. Essas opções podem dar uma entrelinha inesperadamente generosa ao texto pequeno. normal depende da fonte e do navegador, portanto não serve como valor numérico fixo. MDN: valores e herança de line-height

Qual ajuste corresponde ao resultado pretendido?

Escolha o ajuste de acordo com o que os dois rótulos representam. Um cabeçalho compacto composto por rótulos separados tem requisitos diferentes dos de palavras inseridas em um parágrafo com quebra de linha.

Resultado pretendido Ajuste a testar O que verificar
Manter uma linha de base de texto compartilhada Defina uma altura de linha adequada para o intervalo pequeno e alinhe as métricas da fonte do elemento pai com o texto principal A strut do elemento pai deixa de fornecer uma extensão inferior indesejada
Alinhar os topos de rótulos separados Teste vertical-align: top nos dois intervalos em linha A relação de linha de base alterada corresponde ao design
Centralizar rótulos separados dentro de uma linha Use um contêiner flex com align-items: center O comportamento em larguras menores e a quebra dos itens continuam adequados ao layout
Manter o fluxo contínuo do parágrafo Mantenha o texto em linha e use valores herdados de altura de linha definidos intencionalmente Todas as linhas resultantes acomodam o texto de tamanhos mistos

vertical-align: top alinha um elemento e seus descendentes ao topo da linha. middle tem outra definição: alinha o ponto médio do elemento à linha de base do elemento pai mais metade da altura-x do elemento pai. Portanto, não significa centralização geométrica na linha inteira, e alterar o alinhamento não garante uma altura final específica. MDN: valores de vertical-align

Para dois rótulos independentes, o flexbox oferece alinhamento explícito no eixo transversal por meio de align-items. O alinhamento central e o alinhamento pela linha de base expressam escolhas de design diferentes. A mudança para flexbox altera o contexto de layout; use-o quando os rótulos forem itens separados e avalie o resultado em larguras menores. MDN: align-items

Uma altura de linha menor também pode permitir que o texto seja pintado além de sua contribuição para o layout. Verifique as linhas vizinhas depois de reduzir o espaçamento; uma caixa medida compacta, por si só, não comprova que o texto caiba sem sobreposição. W3C: Entrelinha e meia entrelinha

Medições de canvas podem substituir o layout de linha do navegador?

As medições de canvas respondem a uma pergunta relacionada. A WHATWG define measureText() e distingue as métricas de ascendente e descendente da fonte dos limites reais da caixa delimitadora do texto específico. Essas medições são úteis para desenhar em canvas, mas a medição dos limites de tinta não incorpora a strut do elemento pai do DOM que envolve o texto, o alinhamento em linha e as contribuições da altura de linha. Não a use sozinha para prever a altura desta linha. WHATWG: TextMetrics

Preserve os limites de modelagem de texto durante os experimentos. O CSS exige que a modelagem continue através dos limites entre elementos em linha quando não houver mudança efetiva de formatação, enquanto certos limites — incluindo o alinhamento vertical fora do valor inicial — exigem que a modelagem seja interrompida. Evite envolver caracteres individuais para medi-los: separar clusters de grafemas ou ligaduras pode produzir resultados inconsistentes. Mantenha intactos os rótulos completos ou trechos de texto com significado. W3C: processamento e modelagem de texto entre limites de elementos

Para resolver a altura extra, identifique o participante que define o topo e o que define a parte inferior, incluindo o elemento pai. Ajuste as contribuições de altura de linha ou escolha o modelo de alinhamento que corresponda à finalidade dos rótulos e, em seguida, meça novamente a linha completa com o texto e a fonte pretendidos.

Fontes