Saltar al contenido

CSS, de a poco

Curso de CSS

Fuentes

Lección 18 de 37 · 6 min

Una fuente propia tiene que viajar por la red antes de dibujarse. Esta lección cubre cómo se declara, qué ve el lector mientras llega y cómo sacarle todo a un solo archivo variable.

Declarar una fuente

@font-face le pone nombre a un archivo. Después ese nombre se usa en font-family como cualquier otro. El navegador no descarga nada hasta que algún texto de la página lo necesita.

fuentes.css
@font-face {
font-family: "Geist";
src: url(/fonts/geist.woff2) format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
body {
font-family: "Geist", Arial, sans-serif;
}
  1. ArchivosrcAcepta una lista: el navegador usa el primer archivo cuyo format() entiende. Hoy alcanza con woff2. local("Arial") usa una fuente instalada en lugar de descargar.
  2. Rango de pesosfont-weight: 100 900Es un archivo variable que cubre todos esos pesos. Con archivos estáticos se repite el bloque una vez por peso.
  3. Caracteresunicode-rangeDice qué caracteres trae el archivo. Si la página no usa ninguno, no se descarga.

Así carga Geist este mismo sitio: un bloque por alfabeto, todos con el mismo nombre. Un texto en español solo baja el archivo latino; el cirílico y el vietnamita quedan sin descargar.

Los tres archivos de Geist para una página en español: el latino se descarga (29 KB); el cirílico y el vietnamita quedan sin descargar, porque la página no usa ninguno de sus caracteres.
geist.css
@font-face {
font-family: "Geist Variable";
font-display: swap;
font-weight: 100 900;
src: url(./files/geist-latin-wght-normal.woff2) format("woff2-variations");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, /* … */ U+FFFD;
}

Mientras la fuente llega

Mientras el archivo viaja, el texto que lo usa pasa por dos etapas. En el período de bloqueo el texto se dibuja invisible. En el período de intercambio se ve con la fuente de respaldo, y si la buena llega a tiempo, la reemplaza. Si llega después, se queda la de respaldo.

Ver texto invisible se llama FOIT (flash of invisible text); ver el respaldo y después el cambio, FOUT (flash of unstyled text). font-display elige el largo de cada etapa:

  1. EsperarblockBloqueo corto (unos 3s) e intercambio sin fin. Para íconos hechos con fuentes, donde el respaldo mostraría letras sueltas.
  2. Mostrar yaswapBloqueo casi nulo e intercambio sin fin. El texto se lee enseguida; el costo es el salto.
  3. Con plazofallbackBloqueo casi nulo e intercambio corto. Si tarda mucho, ni se molesta en cambiarla.
  4. Solo si ya estáoptionalBloqueo casi nulo y sin intercambio. Si no está casi enseguida, se usa en la próxima visita, ya en caché.
  5. Que decida el navegadorautoEn la práctica suele comportarse como block.

Elegí un valor y mové la llegada de la fuente para ver qué tiene en pantalla el lector en cada momento.

font-display
FOUT · salta a los 1200ms
fuentes.css
@font-face {
font-family: "Geist";
src: url(/fonts/geist.woff2) format("woff2");
font-weight: 100 900;
font-display: swap;
}
body {
font-family: "Geist", Georgia, serif;
}
app.css
/* @font-face va en el CSS de siempre */
@font-face {
font-family: "Geist";
src: url(/fonts/geist.woff2) format("woff2");
font-weight: 100 900;
font-display: swap;
}
@theme {
--font-sans: "Geist", Georgia, serif;
}
Simulación con los plazos que recomienda la especificación (bloqueo corto de 3s, muy corto de 100ms o menos, intercambio corto de 3s). Con auto decide el navegador; se dibuja como block, que es lo más común. El respaldo es una serif a propósito, para que el cambio se note.

font-display es Baseline desde enero de 2020. Para texto de lectura, swap con un respaldo bien ajustado (más abajo) suele ser lo mejor: nadie se queda mirando una página vacía y el salto casi no se nota.

Fuentes variables

Una fuente variable trae ejes en lugar de archivos sueltos: un mismo archivo cubre cualquier peso entre 100 y 900, no solo 400 y 700. Los ejes registrados tienen etiqueta en minúsculas y una propiedad de alto nivel que los mueve:

  1. Pesowght → font-weightCualquier número del rango, como 560.
  2. Anchowdth → font-stretchEn porcentaje, como 87.5% para una versión más angosta.
  3. Tamaño ópticoopsz → font-optical-sizingCon auto ajusta el dibujo al tamaño: trazos más finos y apretados en títulos, más abiertos en texto chico.
  4. Cursiva e inclinaciónital, slnt → font-stylefont-style: italic u oblique 10deg.

font-variation-settings mueve cualquier eje a mano, incluidos los propios de cada fuente, que van en mayúsculas (como "GRAD"). Para los registrados conviene la propiedad de alto nivel: font-variation-settings no se hereda por eje y no le avisa al resto del CSS. Con "wght" 600, font-weight sigue valiendo 400, así que bolder y la elección de archivos no se enteran.

Geist, la fuente de este sitio, solo tiene el eje wght. Mové el peso y cambiá la forma de escribirlo: el dibujo es el mismo, pero el valor que calcula el CSS no.

font-weight: 100 900font-weight: 560

Jovencillo emponzoñado de whisky: ¡qué figurota exhibe!

escribirlo con
muestra.css
/* así carga Geist este sitio (subconjunto latino) */
@font-face {
font-family: "Geist Variable";
font-style: normal;
font-display: swap;
font-weight: 100 900;
src: url(./files/geist-latin-wght-normal.woff2) format("woff2-variations");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
.muestra {
font-family: "Geist Variable", sans-serif;
font-weight: 560;
}
muestra.html
<p class="font-sans font-[560]">…</p>
titulo.css
/* una fuente con más ejes, como Roboto Flex */
.titulo {
font-weight: 650;
font-stretch: 87.5%;
font-optical-sizing: auto;
font-variation-settings: "GRAD" 50; /* eje propio: solo así */
}

font-variation-settings, font-stretch en porcentaje y font-optical-sizing son Baseline desde hace años: entre 2018 y 2020 según la propiedad.

Funciones OpenType

Muchas fuentes traen glifos alternativos que se activan con una etiqueta de cuatro letras: tnum para cifras del mismo ancho, smcp para versalitas, frac para fracciones. Hay dos formas de pedirlas:

  1. De alto nivelfont-variant-*font-variant-numeric: tabular-nums, font-variant-caps: small-caps. Se suman entre sí y, si la fuente no tiene versalitas, el navegador las sintetiza achicando las mayúsculas.
  2. De bajo nivelfont-feature-settings: "tnum"Llega a cualquier etiqueta, pero cada regla reemplaza la lista entera y, si la fuente no trae la función, no pasa nada.

En un horario, las cifras proporcionales de Geist hacen que 11:11 sea más angosto que 10:58 y las columnas no se alinean. Con tabular-nums todas las horas miden lo mismo. Ojo con las versalitas: Geist no trae smcp, así que solo font-variant-caps hace algo.

Un horario de trenes dos veces. Con proportional-nums cada hora mide distinto y los recuadros no se alinean; con tabular-nums todas las horas miden lo mismo.

Un respaldo que no salte

Con swap, el texto se dibuja primero con la fuente de respaldo. Si sus letras son más anchas o más altas, cuando llega la buena las líneas cortan en otro lugar y todo lo de abajo se mueve. Dentro de @font-face hay descriptores para deformar el respaldo hasta que mida casi lo mismo:

  1. Escalasize-adjustEscala los glifos (y el ancho) en porcentaje. Chrome y Firefox lo soportan desde la 92, Safari desde la 17.
  2. Altura de líneaascent-, descent-, line-gap-overrideFijan la altura sobre y bajo la línea de base, que define el alto de cada línea con line-height: normal. Chrome desde la 87 y Firefox desde la 89; Safari todavía no, así que no son Baseline.

El párrafo naranja es Arial, lo que se vería antes de que llegue Geist. A la izquierda, sin tocar; a la derecha, con size-adjust y los overrides: casi no se nota cuál es cuál.

Un párrafo en Geist con el mismo párrafo en Arial encima, en naranja. Sin ajustar, Arial corta las líneas en otro lugar y el párrafo cambia de alto; con size-adjust y los overrides los dos casi coinciden.

Los valores exactos salen de las métricas de las dos fuentes. Herramientas como Capsize (o next/font, que lo hace solo) los calculan por vos; este sitio usa size-adjust: 104.7553% sobre Arial.

Recortar el interlineado

Cada línea de texto reserva lugar arriba y abajo de las letras: el que define la fuente más la mitad del interlineado de cada lado. Por eso un título nunca queda pegado al borde de su caja, y los márgenes que medís en el diseño no coinciden con los del navegador.

text-box-trim recorta ese espacio en la primera línea (trim-start), en la última (trim-end) o en las dos (trim-both). text-box-edge dice hasta dónde: cap corta en la altura de las mayúsculas, ex en la de las minúsculas, alphabetic en la línea de base y text en los bordes de la fuente. El atajo text-box: trim-both cap alphabetic junta las dos.

Un título de 40px dos veces. Sin recorte su caja mide 48px y deja casi 10px arriba de las mayúsculas y abajo de la línea de base; con trim-both cap alphabetic la caja mide lo que las mayúsculas, unos 28px.

Chrome lo soporta desde la 133 y Safari desde la 18.2; los datos de MDN lo dan en Firefox desde la 154, pero todavía no figura como Baseline. Sin soporte, la caja queda con su espacio normal, así que es seguro usarlo: solo cambia el ajuste fino.

En Tailwind

@font-face va en el CSS de siempre. Para usar la familia como clase, registrala en @theme: --font-sans reemplaza la de font-sans, y un nombre nuevo como --font-titulo crea la clase font-titulo.

CSSTailwind
font-weight: 560font-[560]
font-weight: 500 · 700font-medium · font-bold
font-stretch: 87.5%font-stretch-[87.5%]
font-variation-settings: "GRAD" 50[font-variation-settings:'GRAD'_50]
font-variant-numeric: tabular-numstabular-nums
font-variant-caps: small-caps[font-variant-caps:small-caps]
font-feature-settings: "tnum"[font-feature-settings:'tnum']
text-box: trim-both cap alphabetic[text-box:trim-both_cap_alphabetic]
app.css
@font-face {
font-family: "Geist";
src: url(/fonts/geist.woff2) format("woff2");
font-weight: 100 900;
font-display: swap;
}
@theme {
--font-sans: "Geist", "Geist respaldo", sans-serif;
}