text-box-trim y el espacio que no ves
De dónde sale el hueco por encima de las mayúsculas y por debajo de la línea base, los números de una fuente concreta, y cómo recortarlo con soporte y sin él.
La caja de un título es más alta que sus letras, siempre, y por un margen que sorprende: en un titular de 48 píxeles suele haber unos once píxeles de espacio invisible por encima de las mayúsculas. Ese espacio es la razón de que los márgenes de una maqueta nunca coincidan con los de la herramienta de diseño, y de que todos los sistemas de diseño acaben con márgenes negativos mágicos en los títulos.
- Descomponer la altura de una caja de línea en sus métricas.
- Calcular el espacio sobrante por encima y por debajo del texto.
- Aplicar
text-box-trimytext-box-edgecon su condición de soporte. - Construir el recorte manual a partir de las métricas de la fuente.
De dónde sale el espacio
Una fuente declara tres métricas verticales: el ascendente, cuánto sube por encima de la línea base; el descendente, cuánto baja; y el hueco de línea, un espacio extra recomendado. La suma del ascendente y el descendente es la altura de contenido del texto.
Esas métricas no se corresponden con lo que se ve. El ascendente incluye margen para acentos y diacríticos que la mayoría de las letras no usan, así que la altura de las mayúsculas es bastante menor que el ascendente. Y por debajo, el descendente cubre la cola de la p y de la j, que en muchas líneas no aparecen.
Encima de eso, line-height añade su parte. La diferencia entre la altura de línea y la altura de contenido se llama interlínea, y se reparte a partes iguales por arriba y por abajo. Esa repartición es la mitad del problema: el ojo percibe el bloque de texto y no la caja, así que un espacio simétrico alrededor de la caja se ve asimétrico alrededor del texto.
Los números
Tomando una fuente con métricas típicas —ascendente 0.9em, descendente 0.22em, altura de mayúscula 0.7em— y line-height: 1.2:
altura de contenido = 0.90 + 0.22 = 1.12 em
interlinea total = 1.20 - 1.12 = 0.08 em
media interlinea = 0.08 / 2 = 0.04 em
espacio sobre las mayusculas = 0.90 - 0.70 + 0.04 = 0.24 em
espacio bajo la linea base = 0.22 + 0.04 = 0.26 em
A 48 píxeles de tamaño de fuente eso son 11.5 píxeles por encima y 12.5 por debajo, invisibles y contando para el layout. Si el diseño pedía 24 píxeles de separación entre el título y el párrafo, el margen que hay que escribir no es 24: es 24 menos 12.5 menos el espacio que aporte el párrafo por arriba. De ahí salen los márgenes negativos y los números raros de cualquier sistema de diseño maduro.
Y como las métricas son distintas en cada fuente, esos números son constantes específicas de tu tipografía. Cambiar de familia obliga a reajustarlos todos.
text-box-trim y text-box-edge
Las dos propiedades permiten pedir que el navegador recorte ese espacio.
text-box-trim decide qué extremos se recortan: none, trim-start, trim-end o trim-both.
text-box-edge decide hasta dónde: acepta dos valores, el de arriba y el de abajo. Arriba, text deja el ascendente completo, cap recorta hasta la altura de mayúscula y ex hasta la altura de la x. Abajo, text deja el descendente y alphabetic recorta hasta la línea base.
.titulo {
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
}
Con eso, la caja del título empieza exactamente en la parte de arriba de las mayúsculas y acaba exactamente en la línea base. El margin-block que escribas a continuación es el espacio que se ve, sin correcciones.
Existe la abreviatura text-box, que toma los dos a la vez:
.titulo { text-box: trim-both cap alphabetic; }
El soporte no es universal: Chrome y Edge desde la 133 y Safari desde la 18.2; Firefox lo tiene solo en sus versiones de previsualización. Así que va condicionado, y aquí el @supports sí está justificado, porque el recorte y los márgenes compensatorios no pueden convivir:
h2 {
/* base: margen ajustado a ojo para esta fuente */
margin-block: 2rem 0.75rem;
}
@supports (text-box-trim: trim-both) {
h2 {
text-box: trim-both cap alphabetic;
margin-block: 1.5rem 1rem; /* ahora los numeros son los del diseno */
}
}
El recorte manual
Sin soporte, el mismo efecto se consigue con dos pseudo-elementos y márgenes negativos calculados desde las métricas. La técnica se apoya en display: table para que los márgenes no se colapsen con los del bloque.
.titulo {
--ascendente: 0.90;
--descendente: 0.22;
--mayuscula: 0.70;
--altura-linea: 1.2;
line-height: var(--altura-linea);
}
.titulo::before,
.titulo::after {
content: '';
display: table;
}
.titulo::before {
margin-block-end: calc(
-1em * (
(var(--altura-linea) - var(--ascendente) - var(--descendente)) / 2
+ var(--ascendente) - var(--mayuscula)
)
);
}
.titulo::after {
margin-block-start: calc(
-1em * (
(var(--altura-linea) - var(--ascendente) - var(--descendente)) / 2
+ var(--descendente)
)
);
}
Los tres primeros valores salen de la fuente. Se leen de la tabla hhea o de OS/2, dividiendo entre las unidades por em:
python3 -m fontTools.ttx -t head -t hhea -t OS/2 -o - fuente.woff2
unitsPerEm está en head, el ascendente y el descendente en hhea, y la altura de mayúscula en sCapHeight dentro de OS/2.
Una fuente puede declarar métricas verticales en tres sitios —hhea, y dos juegos dentro de OS/2— y cuál usa el navegador depende de un bit de la propia fuente y de la plataforma. Si los números calculados no cuadran con lo que ves, esa discrepancia es la primera sospechosa. La forma empírica de resolverlo es medir: pon un fondo al elemento, haz una captura y cuenta los píxeles hasta la parte alta de las mayúsculas.
Lo que de verdad cambia text-box-trim no es el número de líneas de CSS, es de qué depende tu escala de espaciado. Sin recorte, cada valor de separación entre un título y su párrafo lleva dentro, sumada y sin documentar, una cantidad que no proviene del diseño sino de las métricas de la tipografía concreta: la diferencia entre el ascendente y la altura de mayúscula de esa familia. Ese sumando es invisible en el CSS y por eso nadie lo revisa, hasta el día en que se cambia la fuente y el espaciado de todo el sistema se descoloca sin que nadie sepa por qué, porque en el CSS no hay ni una línea que mencione la fuente. Con el recorte activo, los márgenes vuelven a significar exactamente lo que dicen y el sistema de espaciado pasa a ser independiente de la tipografía, que es la propiedad que hace que un sistema de diseño se pueda mantener: cambiar de familia deja de ser un proyecto y pasa a ser una declaración. Es el mismo tipo de victoria que conseguir que un componente no dependa de su contexto, y merece el mismo esfuerzo, incluida la molestia de mantener durante un tiempo el camino con márgenes ajustados para el motor que todavía no lo soporta.