Cap height: alinhamento vertical de ícones com a unidade cap
Um ícone ao lado de um texto com vertical-align: middle quase nunca fica no meio das letras maiúsculas: ele mira a altura das minúsculas e sai torto.
Com a unidade cap, dá para alinhar qualquer elemento inline ao meio da cap height (a altura das maiúsculas) usando só CSS. São duas técnicas de alinhamento vertical, cada uma com uma linha de cálculo, e um fallback para navegadores sem suporte.
Introdução
O ano novo chegou e, com ele, a temporada em que as pessoas publicam suas listas de desejos de CSS para o futuro. O autor, Roman Komarov, já tinha lido duas delas: a CSS wishlist de dezembro, de Sarah Gebauer, e a “Tyler’s CSS Wish List for 2024”, de Tyler Sticka.
Uma coisa que ele adora fazer diante dessas listas é pensar em todas as formas de já conseguir hoje o que elas pedem.
É sempre um bom desafio, e muitas vezes rende experimentos bem malucos.
Há outros itens para experimentar mais tarde, mas um da lista do Tyler chamou a atenção: a possibilidade de alinhar verticalmente ao meio da cap height da fonte.
O autor já tinha pesquisado isso no passado, e a boa notícia é que existem pelo menos duas soluções razoáveis nos navegadores mais recentes, graças à nova unidade cap!
Nos termos do Baseline, a cap pode ser tratada como “Newly Available”: o Safari 17.2, em dezembro, foi o último navegador principal a implementá-la (veja a tabela completa de compatibilidade no MDN).
É preciso cuidado ao usá-la em produção, mas dá para fazer isso com fallbacks relativamente simples.
Duas técnicas
Tamanho desconhecido, margem negativa
O jeito mais simples de usar a cap para alinhar ao meio da letra maiúscula da fonte (a chamada “cap height”, a mesma linha até onde o text-box-trim apara o texto com o valor cap) é contar com vertical-align: middle e um cálculo simples:
Demonstração: quadrado rosa centralizado na altura das maiúsculas com margem negativa
Quadrado rosa centralizado na altura das maiúsculas. Arraste o canto inferior do quadrado para mudar a altura dele: o alinhamento continua o mesmo.
.example1 .icon { vertical-align: middle; margin-block-start: calc(1ex - 1cap);}Por que subtrair a cap height da x-height? Quando alinhamos algo com vertical-align: middle, estamos alinhando ao meio da x-height da fonte (a altura das minúsculas, que é o que mede a unidade ex). Veja como fica sem a técnica:
Demonstração: quadrado rosa centralizado na altura das minúsculas, desalinhado das maiúsculas
Quadrado rosa centralizado na x-height da fonte. Ele se alinha com a letra "x", mas não com o texto em maiúsculas ao lado.
.example-broken .icon { vertical-align: middle;}De vez em quando esse método pode funcionar, mas só nos casos raros em que há certeza de que o alinhamento é sobre letras minúsculas, sem muitas ascendentes. Na grande maioria das vezes, o que se quer é alinhar à cap height.
Então, se o valor middle alinha à x-height e o desejado é a cap height, basta mover o elemento pela diferença entre as duas! Daí vem o 1ex - 1cap. A altura do próprio elemento já entra na conta pelo valor middle.
Tamanho conhecido, só vertical-align
O exemplo acima funciona bem, mas exige uma margem negativa. Existe outra forma de chegar ao mesmo resultado, desde que a altura do elemento alinhado seja conhecida:
Demonstração: quadrado rosa centralizado na altura das maiúsculas só com vertical-align
Quadrado rosa centralizado na altura das maiúsculas. Arraste o canto do quadrado: só a altura inicial funciona, e o elemento se desalinha quando a altura muda sem ajustar a variável CSS correspondente.
.example2 .icon { --size: min(2em, 10vw); vertical-align: calc(0.5cap - 0.5 * var(--size));}A fórmula aqui é 0.5cap - 0.5 * var(--size): metade da cap height menos metade da altura do elemento. De onde ela sai? Quando o valor de vertical-align é um <length>, o elemento é alinhado de modo que sua borda inferior fique sobre a linha de base (baseline) do texto:
Demonstração: quadrado rosa apoiado na linha de base do texto
Quadrado rosa apoiado na linha de base da fonte. Arraste o canto do quadrado: ele continua na linha de base, qualquer que seja o tamanho.
.example-baseline .icon { vertical-align: 0;}Por que usar esse método, se ele tem a limitação de exigir a altura do elemento?
A resposta: às vezes não se conhece o contexto em que o elemento vai aparecer, e, se ele for parar num contexto com align-items: center, pode não ficar bom! Dá para demonstrar isso colocando os dois exemplos dentro de um contexto flex:
Demonstração: as duas técnicas dentro de um contêiner flex, com a primeira desalinhada
Dois quadrados rosa num contêiner flex: o primeiro, com a margem negativa, fica desalinhado.
.example-flex { display: flex; gap: 0.5em; align-items: center;
& > * { display: contents; }}<div class="example-flex"> <div class="example example1"> <span class="icon"></span> Não Fica Bom </div> <div class="example example2"> <span class="icon"></span> Fica Bom </div></div>Como, nesse caso, o alinhamento fica por conta do flex, o vertical-align deixa de funcionar. Para a segunda técnica isso não importa, porque ele era a única coisa cuidando do alinhamento (que agora é feito pelo flex). Na primeira, porém, a margem continua lá e desalinha o elemento.
Ou seja: se houver certeza de que o elemento vai estar sempre num fluxo inline comum, dá para contar com a margem negativa. Mas, se o tamanho do elemento for conhecido, pode valer mais a pena usar o método que depende só de vertical-align.
Os fallbacks
Como dito no começo, embora a cap seja newly available, ainda não é o caso de usá-la em produção do jeito que está. É cedo demais.
Mesmo assim, em geral não é muito complicado contornar a falta dela, pelo menos em parte.
Um caminho é usar uma variável CSS e @supports. Abaixo está a mesma técnica do primeiro exemplo, adaptada para funcionar sem suporte a cap:
Demonstração: técnica da margem negativa com fallback via @supports
Quadrado rosa centralizado na altura das maiúsculas. Arraste o canto inferior do quadrado: o alinhamento continua o mesmo.
html { /* Ajustado para a Iowan Old Style */ --cap: 0.704583em;}
@supports (height: 1cap) { html { --cap: 1cap; }}
.example-fallback .icon { vertical-align: middle; margin-block-start: calc(1ex - var(--cap));}Basta trocar todas as unidades cap por uma variável var(--cap). Por padrão, ela recebe o valor necessário em em, calculado a partir das métricas da fonte, e o @supports muda esse valor para 1cap onde houver suporte.
E de onde veio o valor 0.704583em? É simples! Agora que a cap existe nos navegadores, obter as proporções de qualquer fonte não dá trabalho nenhum.
O autor criou um quadrado com font-size de 1000px e altura de 1cap, o que resultou em 704.583px. Dividido por 1000, esse número dá o valor da variável!
Claro que o resultado só será perfeito com a fonte usada no teste: a “Iowan Old Style”, que aparece no macOS do autor porque o blog dele usa uma pilha de fontes nativas do sistema.
Num navegador sem suporte a cap e sem a Iowan Old Style instalada, o alinhamento pode quebrar um pouco. Na maioria dos casos isso é aceitável, por ser só um problema visual de alinhamento, que não deve deixar o conteúdo inacessível.
E, com uma web font própria, é bem mais provável que ela esteja sempre presente, então fica mais seguro embutir as métricas dela dessa forma.
Conclusão
É ótimo ver os navegadores melhorando as formas de trabalhar com tipografia na web. Talvez o progresso não seja tão rápido quanto o autor gostaria (a lista de desejos dele é enorme!), mas a chegada da cap (ao lado de outras unidades muito úteis, como ic e lh) abre muitas portas.
Como foi mostrado, as duas técnicas com cap dependem de cálculos e têm seus problemas.
Por isso, continua valendo a recomendação de visitar a issue do Tyler no CSSWG e votar nela, de preferência contando seus casos de uso ou o que pensa da sintaxe nos comentários. Quem sabe um dia isso se resolva com uma única declaração de vertical-align!