wandres.dev
SISTEMAS DE DISEÑO EN CSS · Tokens, temas y modo oscuro

Los tres niveles de token, y por qué tres

Primitivos, semánticos y de componente: qué problema resuelve cada nivel, qué pasa exactamente cuando te saltas uno, y la regla que hace que el sistema absorba los rediseños.

⏱ 18 min

Casi todo el mundo que adopta tokens empieza con un nivel: una lista de colores y espaciados en :root. Casi todo el mundo acaba con tres, y llega a esa conclusión después de un rediseño doloroso. Los tres niveles no son burocracia de sistema de diseño: cada uno resuelve un problema distinto y verificable, y saltarse uno produce un fallo concreto y reconocible. Esta lección explica cuál es el fallo de cada omisión, que es la única forma de que la estructura se entienda en lugar de copiarse.

🎯 Al terminar esta lección sabrás
  • Definir con precisión qué contiene cada uno de los tres niveles.
  • Nombrar el fallo concreto que produce saltarse cada nivel.
  • Aplicar la regla de dirección única entre niveles.
  • Decidir cuándo un valor merece un token de componente y cuándo no.

Los tres niveles

Nivel 1: primitivos. Los valores brutos, nombrados por lo que son. Una rampa de color completa, la escala de espaciado, los radios, los pesos tipográficos. No tienen ningún significado de producto: --azul-600 es un color, no es nada más.

--azul-500: oklch(0.62 0.17 262);
--azul-600: oklch(0.55 0.18 265);
--azul-700: oklch(0.47 0.17 266);
--gris-50:  oklch(0.98 0.004 265);
--gris-900: oklch(0.22 0.012 265);
--esp-1: 0.25rem;
--esp-2: 0.5rem;
--esp-3: 0.75rem;
--esp-4: 1rem;
--radio-md: 0.5rem;

Nivel 2: semánticos. Los valores nombrados por lo que significan en tu producto. No contienen valores literales: apuntan a primitivos.

--color-accion: var(--azul-600);
--color-accion-hover: var(--azul-700);
--superficie: var(--gris-50);
--superficie-elevada: white;
--texto: var(--gris-900);
--texto-tenue: var(--gris-600);
--borde: var(--gris-200);
--espaciado-interior: var(--esp-4);

Nivel 3: de componente. La configuración de un componente concreto, que por defecto apunta a semánticos y existe para poder ajustarse sin tocar nada más.

.boton {
  --boton-fondo: var(--color-accion);
  --boton-texto: white;
  --boton-relleno: var(--esp-2) var(--esp-4);

  background: var(--boton-fondo);
  color: var(--boton-texto);
  padding: var(--boton-relleno);
}

Qué falla al saltarse cada nivel

Esta es la parte que convierte la estructura en algo defendible.

Si te saltas los primitivos y escribes los valores directamente en los semánticos, el fallo es la deriva de la paleta. Sin una rampa cerrada, cada semántico nuevo se define con un color escrito a ojo, y a los seis meses tienes diecinueve grises que se diferencian en el segundo decimal. Nadie lo detecta porque cada uno se añadió por separado y todos parecían razonables. La rampa existe para que el conjunto de valores posibles sea finito y auditable.

Si te saltas los semánticos y los componentes leen primitivos —background: var(--azul-600) dentro del botón— el fallo es que el rediseño se propaga. El día que la acción primaria pasa de azul a verde, tienes que encontrar todos los sitios donde --azul-600 significaba “acción” y distinguirlos de los sitios donde significaba “es azul porque el diseño pedía azul ahí”. Esa distinción no está escrita en ninguna parte, así que hay que reconstruirla mirando pantallas. Es exactamente el trabajo que el nivel semántico existe para evitar, y es la omisión más frecuente y más cara.

Si te saltas los tokens de componente y el componente usa semánticos directamente, el fallo es la excepción que se convierte en global. Llega la petición de que el botón de la barra tenga otro fondo. Como el botón lee --color-accion directamente, las opciones son escribir una regla con un selector más específico —y volver a la escalada— o cambiar --color-accion en el contexto de la barra, con lo que cambias todo lo que sea acción ahí dentro, incluidos los enlaces y los iconos que no querías tocar. El token de componente es lo que permite mover una sola pieza.

/* con token de componente, la excepcion es quirurgica */
.barra .boton { --boton-fondo: var(--superficie-elevada); }
ℹ️
Tres niveles no significa tres veces más tokens

El nivel 3 no se escribe para todo. Solo existe donde se ha necesitado, y se añade cuando aparece la primera excepción real. Un sistema sano tiene muchos primitivos, bastantes semánticos y tokens de componente solo en los cinco o seis componentes que de verdad se configuran desde fuera. Crear los tres niveles para todo por adelantado es el error simétrico y produce un sistema que nadie entiende.

La regla de dirección única

Es la única regla que hay que hacer cumplir, y con ella el sistema se sostiene solo:

Cada nivel solo puede leer del nivel inmediatamente inferior. Los componentes leen semánticos. Los semánticos leen primitivos. Los primitivos no leen nada.

De ahí salen tres prohibiciones concretas:

  • Un componente no lee un primitivo. Si necesita un valor que no existe como semántico, es que falta un semántico, no que haya que hacer una excepción.
  • Un semántico no contiene un valor literal. Si lo contiene, ese valor está fuera de la rampa y no lo audita nadie.
  • Un primitivo no apunta a otro primitivo. Eso crea alias que nadie sabe si son intencionados.

Esto se puede verificar mecánicamente, y merece la pena hacerlo. Una regla de linter que prohíba var(--azul- fuera del bloque de semánticos captura el noventa por ciento de las violaciones y es de las pocas reglas de estilo automatizables que aportan valor real.

La consecuencia que justifica todo el esfuerzo es esta: un cambio de diseño toca exactamente un nivel. Cambiar el color de marca toca primitivos. Cambiar qué significa “acción” toca semánticos. Cambiar un componente toca sus tokens. Si un cambio de diseño te obliga a tocar dos niveles, hay una violación de la regla en alguna parte y merece la pena encontrarla.

Nombrar el nivel semántico

El nivel 2 es donde se gana o se pierde el sistema, y nombrarlo bien es más difícil que los otros dos porque es el único que exige entender el producto.

El criterio que funciona: el nombre describe el papel, no la apariencia ni el sitio.

Mal Por qué Mejor
--gris-claro-fondo describe apariencia --superficie
--color-barra-lateral describe un sitio --superficie-hundida
--azul-boton mezcla primitivo y uso --color-accion
--rojo no dice nada --color-peligro
--espaciado-grande relativo a nada --espaciado-seccion

Los nombres por apariencia se rompen en el modo oscuro, donde --gris-claro-fondo contiene un gris oscuro. Los nombres por sitio se rompen en cuanto el mismo valor se usa en otro sitio, que es siempre. Los nombres por papel sobreviven a los dos.

Un buen conjunto semántico mínimo, para un producto normal, cabe en veinte tokens: superficie en tres profundidades, texto en tres énfasis, borde en dos intensidades, un color de acción con sus estados, y los cuatro tonos de significado —informativo, éxito, aviso, peligro—. Si el tuyo tiene ochenta, probablemente haya tokens de componente disfrazados de semánticos.

El nivel semántico es la única capa que sabe algo de tu producto, y por eso es la que no puedes copiar de nadie

Aquí está el motivo real de que los sistemas de tokens copiados de otro producto nunca terminen de encajar, y conviene entenderlo antes de importar el de moda. Los primitivos son intercambiables: una rampa de doce grises es una rampa de doce grises, la de cualquiera te sirve. Los tokens de componente son locales y se escriben solos según van haciendo falta. Pero el nivel semántico es una ontología de tu producto: la lista de conceptos visuales que tu interfaz distingue. Si tu producto separa “acción primaria” de “acción destructiva confirmada”, eso es un concepto tuyo, y si importas un sistema que no lo tiene te faltará un token y alguien resolverá el hueco con un valor a mano. Al revés es peor y menos visible: si importas un sistema con cuarenta conceptos y tu producto distingue quince, tienes veinticinco tokens que nadie sabe cuándo usar, y la gente elegirá por parecido de nombre, que es aleatorio. Ese es el mecanismo exacto por el que un sistema de tokens grande y bien hecho puede empeorar un producto pequeño. La forma correcta de construir el nivel semántico no es copiarlo ni diseñarlo en abstracto: es mirar la interfaz que ya tienes y anotar cuántas cosas distintas está diciendo de verdad. Casi siempre son menos de las que crees, y el sistema que sale de ese inventario es el único que tu equipo va a usar bien, porque cada nombre corresponde a una distinción que ya tenían en la cabeza.