Tipografía relativa al contenedor
Cómo escalar el texto con el ancho del componente sin romper la accesibilidad: la fórmula de clamp con término en rem, por qué cqi puro incumple WCAG y cómo elegir la pendiente.
Un título que mide 2rem se ve bien en una tarjeta de 700px y ridículo en la misma tarjeta metida en una barra lateral de 280px. La respuesta antigua era una clase modificadora y dos juegos de tamaños; la moderna es hacer que el tamaño sea función del ancho del contenedor. Pero la versión ingenua de esa idea rompe la accesibilidad de una forma concreta y demostrable, y la versión correcta requiere entender por qué clamp() con un término en rem no es un adorno sino el requisito.
- Escribir una escala tipográfica fluida relativa al contenedor con
clamp(). - Explicar por qué un
font-sizeencqipuro incumple el criterio de redimensionado de texto. - Calcular la pendiente y los topes a partir de dos pares de anchos y tamaños.
- Aplicar
line-heightymax-inline-sizecoherentes con un tamaño fluido.
El problema del cqi puro
La versión ingenua es tentadora y hay que descartarla de entrada:
/* NO hagas esto */
.titulo { font-size: 5cqi; }
El tamaño de fuente pasa a depender solo del ancho del contenedor. Un usuario que aumenta el tamaño de letra por defecto en su navegador —porque tiene baja visión, porque está lejos de la pantalla, porque prefiere leer así— no obtiene ningún cambio: el contenedor sigue midiendo lo mismo y el título sigue midiendo el 5% de eso.
Esto es exactamente lo que prohíbe el criterio 1.4.4 de las WCAG, que exige que el texto se pueda ampliar hasta el 200% sin pérdida de contenido ni de funcionalidad. El zoom de página completo sí funciona, porque escala el layout entero; lo que se rompe es el ajuste de tamaño de texto, que es la herramienta que usa quien no quiere que se le agranden también las imágenes y los controles.
El mismo argumento se aplicaba desde siempre a font-size en vw, y la solución es la misma: el valor tiene que contener un término que crezca con el tamaño de fuente de la raíz.
La fórmula correcta
.titulo {
font-size: clamp(1.25rem, 1rem + 2.5cqi, 2.75rem);
line-height: 1.15;
text-wrap: balance;
}
Los tres argumentos hacen tres trabajos distintos y ninguno sobra.
El mínimo en rem es el suelo: por estrecho que sea el contenedor, el texto no baja de ahí, y ese suelo crece si el usuario aumenta la fuente base.
El valor preferido mezcla rem y cqi. El término en rem garantiza que el resultado responde a la preferencia del usuario en todo el rango, no solo en los extremos. El término en cqi es la pendiente: cuánto crece el texto por cada punto porcentual de ancho de contenedor.
El máximo en rem evita que en un contenedor de 1600px el título alcance tamaños de cartel.
La proporción entre los dos términos del centro es la decisión de diseño. Con más rem y menos cqi obtienes una tipografía estable que apenas reacciona; con más cqi obtienes una que cambia mucho. Un reparto de la mitad y la mitad en el punto medio del rango suele ser un buen punto de partida.
Calcular la pendiente a partir de dos puntos
Si tienes claro qué tamaño quieres a dos anchos concretos, la fórmula sale de una recta. Llama A1 y A2 a los anchos de contenedor y T1 y T2 a los tamaños deseados en píxeles.
La pendiente en cqi es 100 * (T2 - T1) / (A2 - A1), y el término constante en píxeles es T1 - pendiente * A1 / 100, que después divides entre 16 para pasarlo a rem.
Con un ejemplo: quieres 20px a 320px de contenedor y 44px a 1200px.
pendiente = 100 * (44 - 20) / (1200 - 320) = 2.727 cqi
constante = 20 - 2.727 * 320 / 100 = 11.27px = 0.705rem
.titulo { font-size: clamp(1.25rem, 0.705rem + 2.727cqi, 2.75rem); }
Comprueba siempre que el valor preferido en el ancho mínimo queda por encima del mínimo del clamp() y que en el ancho máximo queda por debajo del máximo; si no, los topes recortan antes de lo que pretendías y la recta que has calculado no llega a usarse.
Un error frecuente es poner los topes también en unidades de contenedor: clamp(3cqi, 5cqi, 7cqi). Eso no acota nada, porque los tres términos crecen a la vez con el contenedor, y además vuelve a eliminar la respuesta al tamaño de fuente del usuario. Los extremos de un clamp() tipográfico deben estar en rem sin excepción.
Lo que hay que ajustar junto con el tamaño
Un tamaño de fuente fluido arrastra tres decisiones más que la gente suele olvidar.
La altura de línea debe bajar al subir el tamaño. Un line-height de 1.5 que se ve bien a 16px se ve deshilachado a 44px. Lo idiomático es un valor sin unidad más pequeño para los títulos. Si lo quieres fluido hay una restricción de sintaxis que hay que respetar: dentro de clamp() todos los términos deben ser del mismo tipo, y un número no se puede restar de una longitud. La forma válida es usar em, que en line-height se resuelve contra el tamaño de fuente del propio elemento:
/* invalido: mezcla numero y longitud */
/* line-height: clamp(1.05, 1.4 - 0.6cqi, 1.3); */
/* valido: los tres terminos son longitudes */
.titulo { line-height: clamp(1.05em, 1.45em - 1.2cqi, 1.35em); }
Fíjate en el signo: la pendiente es negativa porque quieres que la altura de línea baje cuando el contenedor crece. clamp() admite pendientes negativas siempre que el mínimo siga siendo menor que el máximo.
La medida de línea debe tener tope. Un texto de párrafo que crece con el contenedor acaba con líneas de 140 caracteres, ilegibles. El tope se expresa en ch, que ya es relativo a la fuente:
.cuerpo { font-size: clamp(1rem, 0.9rem + 0.4cqi, 1.125rem); max-inline-size: 65ch; }
El espaciado entre letras debe cerrarse en los tamaños grandes. Las fuentes están diseñadas para un cuerpo de texto; a tamaños de titular el espaciado por defecto se ve suelto. Un letter-spacing ligeramente negativo en los títulos grandes es lo que hace que una tipografía fluida parezca profesional en lugar de simplemente escalada.
Escala completa de un componente
Poniéndolo junto, una escala tipográfica de componente cabe en cuatro custom properties:
.articulo-tarjeta {
--t-titulo: clamp(1.25rem, 0.7rem + 2.7cqi, 2.75rem);
--t-entrada: clamp(1rem, 0.9rem + 0.5cqi, 1.25rem);
--t-cuerpo: clamp(0.95rem, 0.9rem + 0.25cqi, 1.0625rem);
--t-meta: clamp(0.8rem, 0.78rem + 0.1cqi, 0.875rem);
}
.articulo-tarjeta h2 { font-size: var(--t-titulo); line-height: 1.1; letter-spacing: -0.02em; }
.articulo-tarjeta .entrada { font-size: var(--t-entrada); line-height: 1.35; }
.articulo-tarjeta p { font-size: var(--t-cuerpo); line-height: 1.6; max-inline-size: 65ch; }
.articulo-tarjeta .meta { font-size: var(--t-meta); line-height: 1.4; }
Fíjate en las pendientes: el título tiene 2.7cqi y el texto de apoyo 0.1cqi. Esa asimetría es deliberada y es lo que hace que la jerarquía aumente en contenedores grandes en lugar de escalarse en bloque. Un componente donde todo crece en la misma proporción se ve igual de grande, no mejor compuesto.
El motivo por el que tanta tipografía fluida se ve mal no es la técnica: es que se aplica sin haber decidido los extremos. clamp() construye una recta entre dos estados, y una recta solo es tan buena como los dos puntos que la definen. Si no has diseñado deliberadamente cómo debe verse el componente en su tamaño mínimo y en su tamaño máximo, la interpolación va a pasar por un montón de estados intermedios que nadie ha mirado nunca, y el estado intermedio es el que van a ver la mayoría de tus usuarios. La disciplina que separa un sistema fluido bueno de uno improvisado es exactamente la misma que en animación: diseñas los fotogramas clave, no la curva. Diseña la tarjeta a 280px, diséñala a 900px, comprueba los dos, y solo entonces deriva la fórmula que los une. Y después haz lo que casi nadie hace: mira el punto medio, porque si la interpolación lineal no funciona ahí, el problema no se arregla tocando los extremos, se arregla partiendo el rango en dos tramos con una container query en medio.
- Elige un componente real y decide a mano el tamaño de su título a 300px y a 1000px de contenedor.
- Calcula la pendiente y la constante con la fórmula de la recta y escribe el
clamp(). - Verifica en el ancho mínimo y en el máximo que los topes no están recortando antes de tiempo.
- Aumenta el tamaño de fuente por defecto del navegador al 150% y comprueba que el texto crece.
- Sustituye el
clamp()por5cqia secas, repite la prueba anterior y observa la diferencia.