Módulo 2: Design Tokens
Nombrar los tokens por su rol, no por su valor
Descripción
Esta es la lección más corta del módulo y, probablemente, la que más veces te va a salvar en la vida real de un sistema. Trata una sola regla: un token se nombra por el rol que cumple, no por el valor que tiene hoy. color.primary, no blue. color.danger, no red. color.surface, no white. Parece un detalle de estilo —¿qué más da cómo se llame?— y no lo es: es la diferencia entre una capa semántica que sobrevive a un rebranding y una que se rompe con el primer cambio de color.
Ya viste el peligro asomar tres veces. En la presentación del módulo, cambiaste color.primary de azul a verde y sentiste la incomodidad de imaginar un token llamado blue valiendo #16a34a. En la lección 3, viste que la capa semántica existe justo para dar sentido —"el color de marca", no "el azul 600"—. Esta lección toma esa incomodidad y la convierte en una regla dura, con una prueba ejecutada que muestra, en el momento exacto en que la marca cambia de azul a verde, cuál nombre sigue diciendo la verdad y cuál empieza a mentir.
Conexión con el módulo. La lección 3 estructuró los tokens en tres capas; esta afina la regla de nombrado de la capa del medio —la semántica—, que es la que las piezas referencian y la que cambia en el theming. Nombrar por rol es lo que hace que la lección 6 (theming) funcione sin contradicciones: un token llamado color.surface puede valer blanco en claro y casi negro en oscuro sin mentir, mientras que uno llamado white valiendo negro sería un absurdo. Es una regla pequeña con consecuencias en todo lo que sigue.
Una analogía: el rótulo del pocillo del pintor
Vuelve una última vez a la paleta del pintor, porque la analogía cierra aquí con precisión quirúrgica.
Imagina dos pintores que preparan su paleta. El primero rotula cada pocillo por el pigmento que le echó: "azul de ftalocianina", "rojo de cadmio". El segundo los rotula por el papel que cumplen en el cuadro: "cielo", "sombra", "acento".
Mientras el cuadro no cambia, los dos rótulos funcionan igual de bien. Pero a mitad de la obra el pintor decide que el cielo, que venía azul, será un atardecer anaranjado. Los dos vacían el pocillo del cielo y le echan pigmento naranja. Y ahí se separan sus destinos:
- El segundo pintor (rótulos por papel) no toca nada más. Su pocillo sigue diciendo "cielo", y ahora "cielo" es naranja. El rótulo sigue siendo verdad: es el color del cielo, sea cual sea. Sus pinceladas dicen "moja en cielo" y siguen teniendo sentido.
- El primer pintor (rótulos por pigmento) tiene un problema. Su pocillo dice "azul de ftalocianina" pero ahora contiene naranja. El rótulo miente. Para no volverse loco tendría que despegar el rótulo y escribir "rojo... digo, naranja", y luego revisar cada nota que decía "usa azul de ftalocianina aquí" para ver si se refería al cielo (ahora naranja) o de verdad a un azul.
Los dos pintores hicieron el mismo trabajo —cambiar un color—, pero el que rotuló por papel cambió un pocillo y el que rotuló por pigmento heredó una mentira que contamina todas sus notas. Nombrar el token por su rol (color.primary) es rotular por papel; nombrarlo por su valor (blue) es rotular por pigmento. El primero sobrevive al cambio; el segundo se rompe con él.
La regla, y por qué el valor es lo más volátil
La regla en una frase: el nombre de un token describe para qué sirve, no cómo se ve. color.primary dice "el color de las acciones principales / la marca". color.surface dice "el fondo de las superficies". color.text dice "el color del texto". Ninguno menciona un color concreto, y esa es la gracia: pueden cambiar de valor sin que el nombre deje de ser cierto.
¿Por qué tanto cuidado con el nombre? Porque de las dos partes de un token —nombre y valor—, el valor es la más volátil. El color de marca cambia (un rebranding, una campaña, un tema oscuro); el rol "color de marca" no cambia casi nunca —siempre habrá un color de marca, aunque su hex mute—. Si atas el nombre a la parte que cambia (el valor), el nombre queda obsoleto en cuanto el valor se mueva. Si lo atas a la parte estable (el rol), el nombre te sobrevive a todos los cambios de valor. Nombras por rol porque el rol es lo permanente y el valor lo pasajero.
Esto vale para todas las familias de token, no solo el color:
| Nombrado por valor (frágil) | Nombrado por rol (robusto) | Por qué |
|---|---|---|
blue | color.primary | el azul puede volverse verde; "primary" no |
red | color.danger | el rojo del error puede afinarse; "danger" no |
white | color.surface | el fondo puede oscurecerse (dark mode); "surface" no |
size-16 | space-4 | el paso puede reescalarse; su posición en la escala no |
shadow-light | shadow-sm | la sombra puede recalibrarse; "sm" (su nivel) no |
Ojo con una distinción fina. Los primitivos sí se nombran por algo parecido a su valor —blue.600, gray.900—, y está bien: un primitivo es "el azul 600", su trabajo es ser un valor crudo, no cumplir un rol. La regla de nombrar por rol es sobre la capa semántica (y de componente): es ahí donde el nombre debe describir el uso. blue.600 (primitivo, por valor) es correcto; color.primary → blue.600 (semántico, por rol) es correcto; lo que rompe el sistema es usar blue como si fuera el semántico —darle a un rol el nombre de un pigmento—.
Ejemplo trabajado: el nombre que miente
Modelamos el momento exacto del rebranding y le preguntamos a la máquina cuál nombre sigue siendo verdad. Tenemos dos tokens que hoy valen lo mismo (#2563eb): uno nombrado por rol (color.primary) y otro por valor (blue). Una función audit decide si un nombre miente —es decir, si el nombre es una palabra de color que ya no coincide con el color que el token contiene—. Corremos el audit antes y después de que la marca cambie de azul a verde:
// L5 — nombrar por rol vs por valor: que pasa cuando la marca cambia de azul a verde.
const COLOR_WORD = { '#2563eb': 'blue', '#16a34a': 'green' };
const VALUE_WORDS = ['blue', 'green', 'red', 'gray'];
// Un token "miente" si su NOMBRE es una palabra de color que ya no es su valor.
function audit(name, hex) {
const isValueName = VALUE_WORDS.includes(name);
return isValueName && name !== COLOR_WORD[hex];
}
function report(title, name, hex) {
const lies = audit(name, hex);
const tail = lies
? ' <- MIENTE (se llama ' + name + ' pero vale ' + COLOR_WORD[hex] + ')'
: ' ok';
console.log(' ' + title.padEnd(11) + name.padEnd(16) + '= ' + hex + tail);
}
console.log('=== La marca es AZUL (#2563eb) ===');
report('por rol', 'color.primary', '#2563eb');
report('por valor', 'blue', '#2563eb');
console.log('\n=== La marca cambia a VERDE (#16a34a) ===');
report('por rol', 'color.primary', '#16a34a');
report('por valor', 'blue', '#16a34a');
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== La marca es AZUL (#2563eb) ===
por rol color.primary = #2563eb ok
por valor blue = #2563eb ok
=== La marca cambia a VERDE (#16a34a) ===
por rol color.primary = #16a34a ok
por valor blue = #16a34a <- MIENTE (se llama blue pero vale green)
Lee las dos mitades como el antes y el después de un rebranding. Mientras la marca es azul, los dos nombres funcionan: color.primary vale azul (ok) y blue vale azul (ok). Aquí es donde muchos se confunden y creen que da igual cómo nombrar —porque en el estado inicial da igual—. La diferencia no se ve hasta que algo cambia.
Cuando la marca cambia a verde, los caminos se separan, y es exactamente la escena de los dos pintores. color.primary ahora vale #16a34a y sigue marcado ok: su nombre nunca prometió un color, prometió un rol ("el color de marca"), y el color de marca es verde ahora —el nombre sigue diciendo la verdad—. blue, en cambio, ahora vale #16a34a y la máquina lo marca MIENTE: se llama blue pero contiene verde. El nombre prometió un color concreto, y ese color cambió bajo él. Es un token cuyo propio nombre es una contradicción —blue = green—, y esa contradicción se propaga a cada lugar del código que escribió blue pensando "azul de marca".
Fíjate en que la máquina no opinó sobre estética; detectó una mentira: un nombre que ya no describe su contenido. Esa es la falla concreta de nombrar por valor —no es que se vea feo, es que el identificador se vuelve falso—, y por eso envenena todo lo que lo referencia. Cada var(--blue) en el código ahora pinta verde; cada persona que lea --blue esperará azul y se equivocará; cada búsqueda de "el azul de marca" fallará porque el azul de marca ya no es azul.
Una pregunta para que la razones: ¿por qué el token por rol sale ok en las cuatro líneas, incluso después del cambio? (Porque audit solo marca mentira a los nombres que son una palabra de color —blue, green, red, gray—. color.primary no es una palabra de color: es un rol, y un rol no puede "mentir" sobre un color que nunca prometió. Ese es, en una condición de código, todo el argumento de la lección: un nombre por rol es inmune al cambio de valor porque no habla del valor.)
El costo real de un nombre por valor
En un modelo de una línea el nombre blue que miente parece un problema chico —"bueno, lo renombro"—. En un sistema real, renombrarlo es caro y peligroso, y por eso la regla se aplica desde el principio, no cuando ya duele.
Piensa qué implica arreglar un blue que ahora vale verde. Tendrías que: renombrar el token a green (pero entonces mañana que sea morado vuelves a empezar) o —lo correcto— a color.primary; luego buscar todas las referencias var(--blue) en todo el código y cambiarlas a var(--color-primary); y en cada una decidir si esa referencia de verdad quería "el color de marca" o casualmente quería un azul (que ahora no debería moverse). Ese último punto es el veneno: cuando nombras por valor, mezclas dos intenciones distintas —"quiero el color de marca" y "quiero un azul"— bajo el mismo nombre, y al cambiar el valor no puedes distinguirlas sin revisar caso por caso. Un rebranding, que con nombres por rol es cambiar un eslabón (lo viste en la lección 3), con nombres por valor se vuelve una auditoría manual de todo el código.
La regla, entonces, no es cosmética: es lo que mantiene barato el cambio. Nombra por rol desde el primer token y el rebranding sigue costando una línea. Nombra por valor "porque es más literal" y estás firmando una deuda que se cobra, con intereses, el día que un color cambie —y los colores siempre cambian—.
Errores comunes
Nombrar el semántico por su valor "porque es más claro ahora". Qué pasa: se crea color.blue o directamente blue para el color de marca, porque en este momento es azul y el nombre se siente honesto. Por qué pasa: nombrar por lo que ves es lo más literal e inmediato. Cómo detectarlo: tu capa semántica tiene nombres de color (blue, red, green) en vez de roles (primary, danger, success). Cómo corregirlo: lo viste ejecutado —el nombre por valor sale ok hoy y miente en cuanto el valor cambie—. La claridad de hoy es la deuda de mañana. Nombra por el rol (color.primary), que es verdad hoy y después del rebranding. Si te cuesta encontrar el rol, pregúntate: "¿para qué uso este color?" —esa respuesta es el nombre—.
Meter el rol en el nombre del primitivo. Qué pasa: se nombra un primitivo primary.600 en vez de blue.600, contaminando la capa de valores crudos con roles. Por qué pasa: se aplica "nombra por rol" a la capa equivocada. Cómo detectarlo: tus primitivos tienen nombres de rol (primary.600, danger.500). Cómo corregirlo: la regla de nombrar por rol es para la capa semántica, no la primitiva. El primitivo se nombra por lo que es (blue.600), justamente para que pueda ser reutilizado por varios roles sin atarlo a ninguno —color.primary y color.link pueden ambos apuntar a blue.600—. Si el primitivo se llama primary.600, no puedes reusarlo para otro rol sin que su nombre mienta. Primitivo por valor, semántico por rol: cada capa con su convención.
Crear un rol por cada matiz visual en vez de por función. Qué pasa: en el afán de nombrar por rol, se crean color.darkBlue, color.lightBlue, color.brightBlue —que son valores disfrazados de roles—. Por qué pasa: se confunde "describir el color con palabras" con "describir el rol". Cómo detectarlo: tus semánticos describen cómo se ve el color (dark, light, bright) en vez de para qué sirve (primary, surface, text). Cómo corregirlo: lightBlue sigue siendo un nombre por valor, solo que en palabras en vez de en hex —miente igual cuando el azul claro se vuelva otra cosa—. El rol responde "¿qué función cumple?", no "¿de qué tono es?". Si el nombre te dice cómo se ve el color, todavía es un nombre por valor.
Ejercicios
Ejercicio 1 — ¿Por rol o por valor? Para cada nombre de token semántico, di si está nombrado por rol (robusto) o por valor (frágil), y si es frágil, propón el nombre por rol adecuado:
- (a)
color.danger - (b)
green - (c)
color.brightRed - (d)
color.surface
Ver solución
- (a) Por rol.
dangerdescribe la función (error, peligro, acción destructiva), no el color. Robusto: el rojo puede afinarse y el nombre sigue siendo verdad. Correcto. - (b) Por valor.
greenes una palabra de color: miente en cuanto el valor cambie. Nombre por rol según su uso: si es el color de "éxito/confirmación",color.success; si es la marca,color.primary. - (c) Por valor (disfrazado).
brightReddescribe cómo se ve (rojo brillante), no para qué sirve. Es un valor en palabras. Nombre por rol según su función: probablementecolor.danger(ocolor.accentsi es decorativo). - (d) Por rol.
surfacedescribe la función (el fondo de las superficies/tarjetas), no el color. Robusto: puede valer blanco en claro y casi negro en oscuro sin mentir. Correcto.
La prueba de fuego: ¿el nombre sigue siendo verdad si el color cambia? Si sí, es por rol. Si el nombre menciona un color (en hex o en palabras), es por valor y va a mentir.
Ejercicio 2 — Predice el veredicto. Sin correr nada, di qué imprimiría el report para estas dos llamadas, sabiendo que red = #dc2626 según la marca actual pero que la lista COLOR_WORD mapea '#dc2626': 'red' y '#ea580c': 'orange', y que VALUE_WORDS incluye 'red' y 'orange':
report('por valor', 'red', '#ea580c'); // el rojo de error se afino a naranja
report('por rol', 'color.danger', '#ea580c');
Ver solución
por valor red = #ea580c <- MIENTE (se llama red pero vale orange)
por rol color.danger = #ea580c ok
redes una palabra de color (VALUE_WORDSla incluye) y su valor#ea580cmapea aorange, no ared→red !== 'orange'→ miente. El token se llamaredpero contiene naranja.color.dangerno es una palabra de color, así queauditlo deja pasar → ok. El rol "danger" sigue siendo verdad: el color de peligro ahora es un naranja rojizo, y el nombre nunca prometió un tono, solo una función.
Mismo patrón que el ejemplo con azul→verde: el nombre por valor miente al primer cambio, el nombre por rol es inmune.
Ejercicio 3 — Rescata una paleta nombrada por valor. Un equipo definió esta capa semántica nombrando por valor. Reescríbela nombrando por rol, asumiendo los usos entre paréntesis, y explica qué rebranding la habría roto:
const semantics = {
'blue': '#2563eb', // (el color de marca, botones y enlaces)
'white': '#ffffff', // (el fondo de tarjetas y paginas)
'darkGray': '#111827', // (el color del texto)
'red': '#dc2626', // (mensajes de error)
};
Ver solución
Cada nombre por valor se reemplaza por el rol que cumple (y el valor pasa a ser lo que el semántico apunta, idealmente a un primitivo; aquí lo dejamos como valor por brevedad):
const semantics = {
'color.primary': '#2563eb', // el color de marca
'color.surface': '#ffffff', // el fondo de superficies
'color.text': '#111827', // el color del texto
'color.danger': '#dc2626', // el color de error/peligro
};
Qué rebranding la habría roto: cualquiera. Si la marca pasa a verde, blue valdría verde (mentira). Si se agrega tema oscuro, white valdría casi negro (absurdo: white = #111827) y darkGray valdría casi blanco (doble absurdo). Los nombres por valor no solo se rompen con un rebranding de color: se rompen con el simple hecho de tener un segundo tema —que es justo lo que hará la lección 6—. La versión por rol sobrevive a todo eso: color.surface puede valer blanco o negro según el tema, y siempre es "el fondo de las superficies". Nombrar por rol no es una preferencia; es la precondición para que el theming de la próxima lección sea siquiera posible.
Resumen y siguiente paso
En esta lección fijaste la regla más rentable del módulo: un token se nombra por su rol, no por su valor. color.primary, no blue; color.surface, no white. Con los dos pintores viste por qué: el que rotula por papel ("cielo") cambia un pocillo y sigue diciendo la verdad; el que rotula por pigmento ("azul de ftalocianina") hereda una mentira cuando el color cambia. Lo comprobaste ejecutando: al pasar la marca de azul a verde, color.primary siguió ok y blue empezó a mentir —un token cuyo nombre es una contradicción, blue = green, que envenena cada referencia—. Y viste la distinción de capas: los primitivos sí se nombran por valor (blue.600), porque su trabajo es ser un valor; la regla del rol es para la capa semántica, la que las piezas referencian.
Antes de avanzar deberías poder: enunciar la regla; distinguir un nombre por rol de uno por valor (incluidos los disfrazados como lightBlue); explicar por qué el valor es la parte volátil y el rol la estable; y decir por qué los primitivos son la excepción.
La lección 6 cobra todo lo que sembraste: el theming claro y oscuro. Ahora que tus semánticos se llaman por su rol, puedes hacer que color.surface valga blanco en tema claro y casi negro en tema oscuro sin que el nombre mienta y sin tocar un solo componente. Vas a ejecutar resolveToken en los dos temas y ver el mismo token dar distinto valor —el interruptor semántico del que hablamos desde la presentación del módulo, por fin en acción—.
Recursos
- Nathan Curtis, "Naming Tokens in Design Systems" — medium.com/eightshapes-llc/naming-tokens-in-design-systems-9e86c7444676. El tratado de referencia sobre por qué y cómo se nombran los tokens por su rol; amplía cada punto de esta lección. En inglés.
- Design Tokens Community Group (W3C) — design-tokens.github.io/community-group. El estándar que separa los tokens por niveles (y por tanto por convención de nombre): valores crudos vs roles. En inglés.
- Material Design 3, "Design tokens" — m3.material.io/foundations/design-tokens/overview. Cómo un sistema real nombra sus roles (
primary,surface,on-surface) por función y nunca por color. En inglés. - CSS-Tricks, "What Are Design Tokens?" — css-tricks.com/what-are-design-tokens. Ejemplos de nombrado por rol vs por valor y sus consecuencias en el mantenimiento. En inglés.