Módulo 2: Design Tokens

Qué es un design token

Descripción

La presentación del módulo usó la palabra "token" de forma intuitiva: una decisión nombrada una vez, referenciada por muchas piezas. Esta lección le pone una definición formal y ensancha la idea, porque hay un malentendido muy común: creer que los tokens son solo de color. No lo son. Un token puede ser un color, sí —pero también un espaciado, un radio de esquina, una sombra, un tamaño de letra—. Cualquier decisión de diseño que quieras tomar una vez y referenciar en muchos lugares es candidata a ser un token.

La definición corta, la que conviene memorizar: un design token es un valor de diseño con un nombre. Un par nombre → valor. color.primary → #2563eb. space-4 → 16px. radius-md → 8px. El nombre es lo que lo hace referenciable; el valor es lo que produce el estilo. Y la consecuencia grande —la que esta lección demuestra ejecutando— es que el "estilo" de una pieza del storefront deja de ser una lista de valores crudos y se vuelve una lista de referencias a tokens. El Button no es "azul con padding 16 y esquina 8"; es "color.primary de fondo, space-4 de padding, radius-md de esquina". Cada propiedad apunta a un token.

Conexión con el módulo. La lección 1 dio la tesis (un token es un valor nombrado, referenciado); esta la define y muestra su alcance (color, espaciado, radio, sombra, tipografía). Es la base de todo lo que sigue: la lección 3 tomará estos tokens planos y los organizará en tres capas; la 4 los escribirá como CSS custom properties; la 5 fijará cómo nombrarlos; la 7 armará el set completo de Mercado. Aquí instalamos la unidad mínima —el par nombre → valor— sobre la que se construye toda la capa 1.

Una analogía: los pocillos etiquetados de la paleta

Vuelve al pintor del módulo 1, pero mira ahora toda su paleta, no solo los azules. Un pintor con oficio no etiqueta únicamente sus colores. Tiene pocillos para los pigmentos ("cielo", "sombra", "acento"), sí, pero también tiene marcas para otras decisiones que repite: la cantidad de agua que diluye una acuarela ("aguada ligera", "aguada media"), el grosor de trazo de cada pincel ("línea fina", "línea gruesa"), la textura de fondo que aplica siempre igual. Todas son decisiones que tomó una vez y ahora referencia en vez de re-inventar en cada pincelada.

Los tokens son exactamente eso, y por eso no son solo de color:

  • Los pocillos de pigmento son tus tokens de color: color.primary, color.surface, color.text.
  • Las aguadas —cuánta separación deja entre elementos— son tus tokens de espaciado: space-4, space-6.
  • El redondeo del pincel con que suaviza una esquina es tu token de radio: radius-md.
  • La sombra suave que pone bajo un objeto para despegarlo del fondo es tu token de sombra: shadow-sm.
  • El tamaño de la letra con que rotula es tu token de tipografía: text-lg.

Y aquí está lo que la analogía revela: cuando el pintor pinta el marco de un cuadro, no dice "azul cobalto diluido al 30% con trazo de 2mm y sombra suave". Dice "cielo, aguada media, línea fina, sombra suave" —nombra los tokens, no describe los valores—. Su cuadro es una composición de referencias a su paleta. Tu Button es lo mismo: una composición de referencias a tu set de tokens.

Ejemplo trabajado: el Button es una lista de referencias

Para que "el estilo es una lista de referencias" deje de ser una frase y se vuelva algo que se ve, modelamos el estilo del Button de Mercado como lo que es: un objeto donde cada propiedad de CSS no guarda un valor crudo, sino el nombre de un token. Luego una función token() los resuelve a sus valores. Fíjate en que hay tokens de cinco categorías distintas —color, color, espaciado, radio, sombra— y todos se referencian igual:

// L2 — un token es un valor con nombre; el "estilo" de una pieza son REFERENCIAS a tokens.
const tokens = {
  'color.primary': '#2563eb',
  'color.surface': '#ffffff',
  'color.text':    '#111827',
  'space-4':       '16px',
  'space-6':       '24px',
  'radius-md':     '8px',
  'shadow-sm':     '0 1px 2px rgba(0,0,0,0.05)',
};

function token(name) {
  if (!(name in tokens)) throw new Error('Token desconocido: ' + name);
  return tokens[name];
}

// El estilo de un Button no es una lista de valores crudos: es una lista de referencias.
const buttonStyle = {
  background:   'color.primary',
  color:        'color.surface',
  padding:      'space-4',
  borderRadius: 'radius-md',
  boxShadow:    'shadow-sm',
};

console.log('=== Button, resuelto desde tokens ===');
for (const [prop, tokenName] of Object.entries(buttonStyle)) {
  console.log('  ' + prop.padEnd(13) + tokenName.padEnd(16) + '-> ' + token(tokenName));
}

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== Button, resuelto desde tokens ===
  background   color.primary   -> #2563eb
  color        color.surface   -> #ffffff
  padding      space-4         -> 16px
  borderRadius radius-md       -> 8px
  boxShadow    shadow-sm       -> 0 1px 2px rgba(0,0,0,0.05)

Lee la salida columna por columna, porque cuenta la anatomía de un token. La primera columna es la propiedad de CSS que el Button quiere estilar (background, padding, …). La segunda es el token que referencia para esa propiedad —no un valor, un nombre—. La tercera es el valor al que ese token resuelve. El Button "sabe" que su fondo es color.primary; lo que color.primary vale hoy (#2563eb) es asunto del token, no del botón.

Fíjate en dos cosas que este ejemplo hace evidentes. Primero: los tokens no son solo de color. Aquí hay cinco propiedades y usan tokens de cuatro familias —color (color.primary, color.surface), espaciado (space-4), radio (radius-md) y sombra (shadow-sm)—. Cualquier decisión de diseño repetible es un token, no solo el azul de marca. Segundo, y más importante: el Button no contiene ni un solo valor crudo. No hay un #2563eb ni un 16px escrito en él. Todo lo que el botón declara son nombres de tokens. Si mañana space-4 pasa de 16px a 20px, el padding del Button cambia sin que nadie toque el Button —y con él, el de toda pieza que referencie space-4—.

Una pregunta para que la razones mientras lees la salida: si shadow-sm cambiara su valor, ¿qué piezas del storefront se actualizarían? (Todas las que referencien shadow-sm —el Button de este ejemplo y cualquier Card o Badge que lo use—, y solo esas. El cambio viaja exactamente hasta donde llega la referencia, ni un paso más. Esa es la misma propiedad del flujo hacia arriba del módulo 1, ahora a nivel de un solo token.)

Qué puede (y qué no) ser un token

No todo valor merece ser un token, y saber la diferencia te evita dos extremos igual de malos: tener demasiados o demasiado pocos.

Buenos candidatos a token son los valores que cumplen dos condiciones: se repiten (aparecen en muchos lugares) y representan una decisión de diseño (alguien podría querer cambiarlos de forma coherente). El azul de marca cumple las dos: aparece en botones, enlaces, badges, y es una decisión que puede cambiar. El espaciado medio (space-4) cumple las dos. El radio de las tarjetas cumple las dos. Estos son tokens.

Malos candidatos son los valores únicos o accidentales: el top: 3px que ajusta un ícono específico para que quede alineado en ese lugar, el width: 342px de un contenedor que salió de una medida puntual. No se repiten y no son decisiones de diseño reutilizables; son ajustes locales. Convertirlos en tokens solo infla el sistema con nombres que nadie más va a referenciar. Déjalos como valores locales; no todo tiene que ser un token.

Las categorías de token que verás en Mercado —y en casi cualquier sistema— son estas: color (marca, superficie, texto, estados), espaciado (la separación entre y dentro de los elementos), tipografía (tamaños y pesos de letra), radios (el redondeo de las esquinas) y sombras (la elevación). Espaciado y tipografía tienen además una estructura de escala —no son valores sueltos sino una progresión ordenada— y esa escala es el tema del módulo 4; aquí basta con saber que space-4 y text-lg son tokens, como lo es color.primary.

Y una precisión que la lección 3 desarrollará: los tokens de este ejemplo están todos planos —cada uno es directamente un valor—. En un sistema real hay una estructura por debajo: unos tokens son valores crudos (primitivos), otros apuntan a esos crudos (semánticos), otros apuntan a los semánticos (de componente). Ese es el siguiente escalón. Por ahora quédate con la unidad mínima: un token es un par nombre → valor, y la UI se construye referenciándolos.

Errores comunes

Creer que los tokens son solo de color. Qué pasa: se arma una paleta de colores con nombres bonitos y se cree que "ya está el sistema de tokens", mientras los espaciados, radios y sombras se siguen escribiendo a mano. Por qué pasa: el color es lo más visible y lo primero que uno piensa al oír "design system". Cómo detectarlo: tienes color.primary y color.surface como tokens, pero cada componente escribe su padding: 16px y su border-radius: 8px directo. Cómo corregirlo: el espaciado, el radio, la sombra y la tipografía sufren drift igual que el color —lo viste en el módulo 1: el padding tenía dos valores donde debía tener uno—. Todo valor de diseño repetible es candidato a token. Un sistema con tokens de color pero espaciados ad-hoc es media cura.

Convertir en token cada valor que aparece. Qué pasa: en el afán de "todo debe ser un token", se crea un token para el top: 3px de un ícono y el width: 342px de un contenedor puntual. Por qué pasa: la regla "referencia, no copies" se aplica sin la pregunta previa —¿esto se repite? ¿es una decisión de diseño?—. Cómo detectarlo: tu lista de tokens tiene nombres que solo se usan una vez, como icon-cart-top-offset. Cómo corregirlo: un token justifica su existencia cuando se repite y representa una decisión. Los ajustes locales y únicos se quedan como valores locales. Demasiados tokens es tan dañino como demasiado pocos: nadie los recuerda y el sistema se vuelve un diccionario inmanejable.

Escribir el valor crudo "solo esta vez". Qué pasa: se tiene el token color.primary, pero en un componente nuevo se escribe #2563eb directo "porque es más rápido ahora". Por qué pasa: en el momento, teclear el hex es más rápido que recordar el nombre del token. Cómo detectarlo: buscas #2563eb en el código y aparece además de en la definición del token. Cómo corregirlo: cada valor crudo escrito fuera de la definición del token es una futura fuente de drift —una copia que nacerá idéntica y divergirá—. Lo viste ejecutado en el módulo 1. La regla es dura: fuera del lugar donde se define el token, nunca aparece su valor crudo; solo su nombre. Si te descubres tecleando un hex o un px en un componente, ahí falta una referencia a un token.

Ejercicios

Ejercicio 1 — ¿Token o valor local? Para cada valor, di si merece ser un token (se repite + es una decisión de diseño) o si debería quedarse como valor local (único/accidental):

  • (a) El azul #2563eb que usan botones, enlaces y badges.
  • (b) El margin-top: 2px que baja un ícono para alinearlo en el header.
  • (c) La separación 16px que se repite entre casi todos los elementos del storefront.
  • (d) El width: 728px de un banner publicitario de un tamaño estándar de anuncio.
Ver solución
  • (a) Token. Se repite (botones, enlaces, badges) y es una decisión de diseño (el color de marca, que puede cambiar de forma coherente). Es color.primary.
  • (b) Valor local. Es un ajuste puntual y único para alinear ese ícono en ese lugar. No se repite ni representa una decisión reutilizable. Déjalo como valor local; hacerlo token solo infla el sistema.
  • (c) Token. Se repite por todo el storefront y es una decisión de diseño (el espaciado base). Es space-4. Que sea de espaciado y no de color no cambia nada: es igual de token.
  • (d) Valor local (discutible). Es un tamaño estándar externo (un formato de anuncio), no una decisión de tu diseño ni algo que quieras cambiar de forma coherente con el resto. Se queda como valor local. La prueba: ¿lo cambiarías junto con otros valores del sistema? No —lo fija el formato del anuncio—.

La pregunta guía siempre es doble: ¿se repite? y ¿es una decisión de diseño que quiero poder cambiar de forma coherente? Solo si las dos son "sí", es token.

Ejercicio 2 — Predice la salida. Sin correr nada, di qué imprimiría el ejemplo trabajado si al buttonStyle le agregamos una propiedad más y cambiamos el valor de un token:

tokens['space-6'] = '20px';                 // la escala cambia
buttonStyle.marginBottom = 'space-6';       // el Button ahora separa por abajo

¿Qué línea nueva aparece y con qué valor?

Ver solución

Aparecería una línea nueva al final del listado:

  marginBottom space-6         -> 20px

Dos cosas que confirma: primero, el Button referencia space-6, así que muestra el valor actual del token —20px, el que le acabamos de poner, no el 24px original—. Segundo, agregar una propiedad al buttonStyle es solo agregar una referencia más; el mecanismo (buscar el token con token() y mostrar su valor) no cambia. El estilo del Button sigue siendo una lista de referencias, ahora con un elemento más.

Ejercicio 3 — Reescribe el CSS crudo como referencias. Aquí está el estilo de una Card del storefront escrito con valores crudos. Reescríbelo como una lista de referencias a tokens, usando el set del ejemplo trabajado (color.surface, color.text, space-6, radius-md, shadow-sm). Indica qué token va en cada propiedad.

.card {
  background: #ffffff;
  color: #111827;
  padding: 24px;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
Ver solución

Cada valor crudo se reemplaza por el token cuyo valor coincide:

PropiedadValor crudoToken que lo reemplaza
background#ffffffcolor.surface
color#111827color.text
padding24pxspace-6
border-radius8pxradius-md
box-shadow0 1px 2px rgba(0,0,0,0.05)shadow-sm

Como objeto de referencias, igual que el buttonStyle del ejemplo:

const cardStyle = {
  background:   'color.surface',
  color:        'color.text',
  padding:      'space-6',
  borderRadius: 'radius-md',
  boxShadow:    'shadow-sm',
};

Fíjate en lo que ganaste: la Card ya no contiene ni un valor crudo. Comparte color.surface, radius-md y shadow-sm con el Button —las dos piezas beben de los mismos tokens—, así que cambiar cualquiera de esos tokens actualiza a las dos a la vez. Eso es imposible cuando cada una tiene sus hex y sus px escritos a mano: ahí no comparten nada, y divergen. Reescribir crudo → referencias es, literalmente, convertir dos piezas sueltas en dos piezas del mismo sistema.

Resumen y siguiente paso

En esta lección le pusiste definición formal a la pieza más baja del sistema: un design token es un valor de diseño con un nombre —un par nombre → valor— y no es solo de color. Hay tokens de color, de espaciado, de tipografía, de radio y de sombra: cualquier decisión de diseño que se repita y que quieras poder cambiar de forma coherente. Con los pocillos etiquetados del pintor viste que un cuadro —como un Button— es una composición de referencias a la paleta, no una lista de valores crudos. Y lo comprobaste ejecutando: el estilo del Button de Mercado resultó ser cinco propiedades, cada una apuntando a un token de una familia distinta, sin un solo hex ni px escrito en el componente.

Antes de avanzar deberías poder: dar la definición corta de token; nombrar al menos cuatro familias de token; distinguir un buen candidato a token (se repite + es decisión) de un valor local; y explicar por qué el estilo de una pieza es una lista de referencias.

La lección 3 da el salto estructural del módulo: las tres capas de tokens. Hasta aquí todos los tokens fueron planos —cada uno, directamente un valor—. Pero en un sistema real hay una jerarquía: unos tokens son valores crudos (primitivos, como blue.600), otros apuntan a esos crudos por su rol (semánticos, como color.primary), y otros atan un rol a una pieza (de componente, como button.bg). Vas a ver la cadena button.bg → color.primary → blue.600 → #2563eb y a escribir el resolveToken que la recorre hasta el valor final. Es el mecanismo que hace posible todo lo que sigue, incluido el theming.

Recursos