InnovaWebb, ir al inicio

Tokens de diseño y contraste con Tailwind v4

Durante el desarrollo de la Fase 1 del sitio web de InnovaWebb, tomamos la decisión de no empezar a programar componentes sin antes definir unas reglas claras para el sistema visual.

No se trata solo de elegir colores bonitos. Se trata de que esos colores tengan un nombre y un propósito, y que garanticen que cualquier persona, independientemente de su capacidad visual o el tipo de pantalla que use, pueda leer el sitio sin esfuerzo.

Así fue como estructuramos los tokens de diseño y configuramos Tailwind CSS v4 para el proyecto.

¿Por qué usar Tokens de Diseño?

Un token de diseño es un nombre que se le asigna a un valor para explicar su función. En lugar de utilizar un valor "duro" en el código, usamos ese nombre.

Por ejemplo, si tenemos el color hexadecimal #00B7D0 que representa el cian principal de InnovaWebb, podríamos agregarlo al código de esta manera:

button {
  background-color: #00B7D0;
}

Pero en lugar de eso, creamos un token en la raíz del proyecto así:

:root {
  --color-brand-cyan: #00B7D0;
}

Donde el nombre --color-brand-cyan corresponde a ese valor. Después podemos utilizar ese nombre (o token) en cualquier parte:

button {
  background-color: var(--color-brand-cyan);
}

Los tokens hacen explícito el sistema. En pocas palabras, significan que los tokens dicen cuáles son las reglas visuales del sitio web sin tener que adivinar qué significa un código hexadecimal.

La magia de Tailwind v4 y @theme

La primera diferencia que importa es la que hay entre Tailwind v3 y Tailwind v4. Básicamente, en Tailwind 3 era necesario tener un archivo enorme llamado tailwind.config.js donde se configuraban colores, fuentes, tamaños, etc.

En Tailwind v4, gran parte de esa configuración puede vivir directamente en el archivo CSS dentro de un bloque llamado @theme.

Así se ve en nuestro archivo globals.css:

@import "tailwindcss";

@theme {
  --color-brand-cyan: #00B7D0;
  --font-display: var(--font-poppins), sans-serif;
}

En pocas palabras, el archivo CSS no solamente contiene estilos, también contiene la definición del sistema de diseño para Tailwind CSS.

Lo que hace @theme se puede explicar así: cuando escribimos --color-brand-cyan: #00B7D0;, le decimos a Tailwind "Quiero crear un color de diseño llamado brand-cyan, cuyo valor es #00B7D0". Tailwind reconoce que es un color por el prefijo --color-.

A partir de ese nombre, genera las utilidades correspondientes automáticamente:

  • <div className="bg-brand-cyan"> significa: usa --color-brand-cyan como color de fondo.
  • <p className="text-brand-cyan"> significa: usa --color-brand-cyan como color de texto.
  • <div className="border-brand-cyan"> significa: usa --color-brand-cyan para el borde.

La regla innegociable: El contraste

Tener los colores definidos en un solo lugar no sirve de nada si esos colores no se pueden leer. Y aquí está la lección más importante de estructurar el diseño de InnovaWebb: la autoridad de quien lo dice no es evidencia. El contraste se mide, no se percibe.

A simple vista, el texto blanco sobre un fondo cian (#00B7D0) parece perfectamente legible. Pero no lo es.

Al medirlo, el texto blanco sobre #00B7D0 da 2,42:1. La norma WCAG exige 4,5:1 para texto normal. No está cerca: está a mitad de camino.

Lo que lo arregla no es cambiar el cian, porque el cian es la marca. Es cambiar qué se escribe encima:

CombinaciónContraste¿Pasa?
Blanco sobre cian #00B7D02,42:1❌
Tinta #0B1F2E sobre cian6,95:1✅

De ahí salió una regla que el proyecto no ha vuelto a romper: el cian es un color de relleno, nunca un color de texto sobre fondo claro. Para escribir en cian existe un token aparte, --color-brand-cyan-text (#007A8C), que es el mismo tono oscurecido hasta llegar a 5,05:1.

Y una propiedad del contraste que conviene saber, porque ahorra la mitad de las mediciones: es simétrico. La fórmula es (L1 + 0,05) / (L2 + 0,05) y no distingue cuál de los dos colores es el fondo. Si A sobre B pasa, B sobre A pasa con el mismo número.

Qué pasó después

El modo oscuro llegó en la Fase 2, y la apuesta salió bien.

La razón es exactamente la de este artículo. Como los colores no estaban escritos dentro de los componentes sino detrás de tokens con nombre, añadir el tema oscuro consistió en redefinir esos nombres dentro de un bloque @media (prefers-color-scheme: dark). Los componentes apenas se tocaron: en todo el sitio hicieron falta cuatro clases dark:, y todas son para cambiar un logo por su versión en negativo.

Lo que no ahorró el sistema fue la medición del contraste, que hubo que repetir sobre el doble de combinaciones. Los tokens evitan reescribir código; no evitan verificar que se lee.

Lecciones que quedan

  1. El CSS más específico siempre gana: Al aplicar fuentes globales en layout.tsx hay que asegurarse de que no haya una regla de font-family genérica en el body del archivo globals.css que pise nuestro trabajo.
  2. Las decisiones se documentan: En esta fase decidimos no implementar el "modo oscuro" inmediatamente. Es mejor salir a producción con un modo claro bien hecho y evaluarlo en la Fase 2, que retrasar el lanzamiento del sitio por duplicar las revisiones de contraste visual.

Al final del día, la configuración del sistema de diseño nos permitió construir todas las páginas de la Fase 1 mucho más rápido y sin errores de inconsistencia visual.

← Todos los artículos