Módulo 2: Design Tokens
Los tokens en la práctica: la paleta de Mercado
Descripción
Las seis lecciones anteriores te dieron las piezas por separado: qué es un token, las tres capas, las custom properties, el nombrado por rol, el theming. Esta lección las junta en el caso real y las mira funcionar de punta a punta: la paleta de tokens del storefront de Mercado, sus tres capas completas, resuelta en tema claro y oscuro. No hay concepto nuevo aquí —es el ensayo general antes del proyecto—; el valor está en ver el sistema entero de una vez, y en que cada parte ocupe exactamente el lugar que las lecciones anteriores le asignaron.
Es también donde el módulo se vuelve concreto sobre Mercado. En el módulo 1 hiciste el inventario del sistema (la lista de piezas que el storefront necesita); ahora ese inventario, en su capa más baja, tiene valores reales: los primitivos con sus hex, los semánticos apuntando a ellos con override por tema, los tokens de componente atando las piezas del storefront a sus roles. Al terminar esta lección tendrás delante el set de tokens con el que un Button, una Card y una SearchBar de Mercado se ven coherentes y soportan modo oscuro —el mismo set que el proyecto te pedirá construir tú—.
Conexión con el módulo. Esta lección es la síntesis de las seis anteriores aplicada a Mercado. Reúne las tres capas (L3), el nombrado por rol (L5) y el theming (L6) en un solo set, y lo resuelve entero como preparación directa del proyecto (L8). También conecta hacia atrás con el módulo 1: pone valores reales a la capa de tokens del inventario que hiciste allá. Es el "todo junto" antes de que tú lo construyas.
Una analogía: la paleta terminada del pintor, lista para el cuadro
Durante todo el módulo fuiste preparando la paleta del pintor pocillo por pocillo. Esta lección es el momento en que el pintor da un paso atrás y mira su paleta completa antes de empezar el cuadro: los tubos de pigmento crudo alineados (los primitivos), los pocillos etiquetados por su papel —"marca", "fondo", "texto"— cada uno conectado a su pigmento (los semánticos), y las notas al margen que dicen "el botón usa marca, la tarjeta usa fondo" (los de componente). Y al lado, dos hojas de referencia: cómo se ve cada pocillo con luz de día y con luz de noche (los dos temas).
Con esa paleta a la vista, el pintor ya no improvisa: cada pincelada del cuadro es "moja en tal pocillo". Tú estás en el mismo punto con Mercado. La paleta está armada; lo que sigue (los módulos 3 en adelante) es pintar —construir las utilidades, los componentes, los patrones— mojando en estos tokens. Esta lección es la última mirada a la paleta completa antes de que el pincel toque el lienzo.
La paleta de Mercado, capa por capa
Antes de ejecutarla, veamos el diseño del set y por qué cada token está donde está.
Primitivos — la caja de pigmentos. Mercado necesita un azul de marca y su versión para fondo oscuro (blue.600, blue.400), y una rampa de grises para fondos y texto (gray.50, gray.900), más el blanco puro (white). Se nombran por familia y paso, sin rol —son valores crudos, iguales en todo tema—.
Semánticos — los roles del storefront, con override por tema:
color.primary— el color de marca (botones principales, enlaces, badges). Claro:blue.600; oscuro:blue.400(un azul más luminoso, que resalta sobre fondo oscuro).color.surface— el fondo de las superficies (tarjetas, paneles, la página). Claro:white; oscuro:gray.900.color.text— el color del texto principal. Claro:gray.900; oscuro:gray.50. Fíjate en la inversión respecto asurface: donde el fondo es claro, el texto es oscuro, y viceversa.
De componente — las piezas del storefront atadas a sus roles:
button.bg → color.primary(el fondo del botón usa el color de marca).card.bg → color.surface(el fondo de la tarjeta usa el color de superficie).card.text → color.text(el texto de la tarjeta usa el color de texto).
Fíjate en lo que este set no tiene, porque es tan importante como lo que tiene. No hay ningún token nombrado por valor (nada de blue como semántico; los blue.* son solo primitivos, L5). No hay override de tema fuera de la capa semántica (los primitivos son fijos, los de componente heredan; L6). No hay un valor crudo repetido: cada hex vive en un solo primitivo, y todo lo demás apunta hacia él (L2, L3). Es un inventario limpio de la capa 1 —el mismo criterio con el que en el módulo 1 detectaste el drift, ahora aplicado a la definición del sistema—.
Una nota sobre el alcance: este set es deliberadamente pequeño —cinco primitivos, tres semánticos, tres de componente—, lo justo para el storefront básico. Un Mercado real tendría más roles (color.danger, color.muted, color.border), una rampa de grises más larga, y tokens de espaciado y tipografía —pero esos últimos pertenecen a una escala (cuántos pasos, qué ratios), que es el módulo 4—. Aquí nos concentramos en el color, que es donde las tres capas y el theming se ven con más claridad. Ampliar el set es agregar más tokens de la misma forma, no cambiar el mecanismo.
Ejemplo trabajado: la paleta completa, resuelta
Ejecutamos el set entero: imprimimos las tres capas (con sus conteos) y luego resolvemos los tokens de componente en ambos temas. Es la misma estructura y el mismo resolveToken de las lecciones 3 y 6, aplicados al set real de Mercado:
// L7 — la paleta de Mercado, las 3 capas juntas y resueltas en ambos temas.
const tokens = {
primitives: {
'blue.600': '#2563eb',
'blue.400': '#60a5fa',
'gray.50': '#f9fafb',
'gray.900': '#111827',
'white': '#ffffff',
},
semantics: {
'color.primary': { light: 'blue.600', dark: 'blue.400' },
'color.surface': { light: 'white', dark: 'gray.900' },
'color.text': { light: 'gray.900', dark: 'gray.50' },
},
component: {
'button.bg': 'color.primary',
'card.bg': 'color.surface',
'card.text': 'color.text',
},
};
function resolveToken(name, theme) {
if (name in tokens.component) return resolveToken(tokens.component[name], theme);
if (name in tokens.semantics) return resolveToken(tokens.semantics[name][theme], theme);
if (name in tokens.primitives) return tokens.primitives[name];
throw new Error('Token desconocido: ' + name);
}
console.log('=== Paleta de Mercado ===\n');
console.log('TIER 1 primitivos (' + Object.keys(tokens.primitives).length + '): valores crudos');
for (const [name, value] of Object.entries(tokens.primitives)) {
console.log(' ' + name.padEnd(10) + value);
}
console.log('\nTIER 2 semanticos (' + Object.keys(tokens.semantics).length + '): rol -> primitivo por tema');
for (const [name, byTheme] of Object.entries(tokens.semantics)) {
console.log(' ' + name.padEnd(14) + 'light: ' + byTheme.light.padEnd(9) + 'dark: ' + byTheme.dark);
}
console.log('\nTIER 3 de componente (' + Object.keys(tokens.component).length + '): pieza -> semantico');
for (const [name, target] of Object.entries(tokens.component)) {
console.log(' ' + name.padEnd(12) + '-> ' + target);
}
console.log('\n=== Resueltos hasta el valor final ===');
console.log('token'.padEnd(16) + 'light'.padEnd(12) + 'dark');
console.log('-'.repeat(38));
for (const name of ['button.bg', 'card.bg', 'card.text']) {
console.log(name.padEnd(16) + resolveToken(name, 'light').padEnd(12) + resolveToken(name, 'dark'));
}
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== Paleta de Mercado ===
TIER 1 primitivos (5): valores crudos
blue.600 #2563eb
blue.400 #60a5fa
gray.50 #f9fafb
gray.900 #111827
white #ffffff
TIER 2 semanticos (3): rol -> primitivo por tema
color.primary light: blue.600 dark: blue.400
color.surface light: white dark: gray.900
color.text light: gray.900 dark: gray.50
TIER 3 de componente (3): pieza -> semantico
button.bg -> color.primary
card.bg -> color.surface
card.text -> color.text
=== Resueltos hasta el valor final ===
token light dark
--------------------------------------
button.bg #2563eb #60a5fa
card.bg #ffffff #111827
card.text #111827 #f9fafb
Lee la salida como la paleta completa que es, de abajo hacia arriba. Tier 1 son los cinco pigmentos crudos: cada nombre es una familia y un paso, cada valor un hex fijo. Ninguno menciona un rol; son la materia prima. Tier 2 son los tres roles, y cada línea muestra las dos flechas —a qué primitivo apunta en claro y en oscuro—: color.surface va a white en claro y a gray.900 en oscuro. Aquí, y solo aquí, vive el theming. Tier 3 son las tres piezas del storefront atadas a roles: cada una apunta a un semántico, sin valores ni temas propios.
La forma de la paleta cuenta algo: hay más primitivos (5) que roles (3), y los mismos tres roles se atan a tres piezas. Es la pirámide del módulo 1 dentro de la capa de tokens —una base ancha de valores crudos, una capa media de roles que elige de ella, una capa de piezas que referencia los roles—. Y fíjate en que no todos los primitivos se usan como valor final en la tabla: gray.50 aparece resuelto (es color.text en oscuro), pero la caja de pigmentos siempre tiene más de lo que un tema concreto usa —igual que el pintor tiene tubos que no toca en este cuadro—.
La tabla final es la prueba de que todo el sistema resuelve. Los tres tokens de componente, en los dos temas, dan seis valores —y son coherentes con el theming de la L6: button.bg sigue a color.primary (azul en ambos temas, más luminoso en oscuro), card.bg y card.text se invierten entre temas (fondo claro/texto oscuro en claro, al revés en oscuro)—. Un Button, una Card: dos piezas del storefront, seis valores resueltos, dos temas, y ni un solo valor crudo escrito en un componente. Esa es la paleta de Mercado lista para pintar.
Una pregunta para cerrar el ensayo: si Mercado agregara un Badge de oferta que usa el color de marca de fondo, ¿qué token de componente crearías y a qué apuntaría —y cuántos valores nuevos tendrías que escribir? (Crearías badge.bg → color.primary, apuntando al rol existente. Valores nuevos: cero —el Badge hereda el azul de marca en los dos temas del semántico que ya existe—. Agregar una pieza al sistema es atarla a un rol, no inventar valores. Ese es el ahorro compuesto de las tres capas.)
Errores comunes
Repetir un valor en dos primitivos. Qué pasa: al armar la caja de pigmentos se cuelan dos primitivos con el mismo (o casi el mismo) hex —blue.600 = #2563eb y brand.blue = #2563eb—. Por qué pasa: se agregan primitivos sobre la marcha sin revisar la caja contra sí misma. Cómo detectarlo: dos nombres primitivos distintos resuelven al mismo valor, o a valores imperceptiblemente distintos —el drift del módulo 1, ahora en la definición—. Cómo corregirlo: un valor, un primitivo. Si dos roles necesitan el mismo azul, los dos semánticos apuntan al mismo primitivo; no se crean dos primitivos gemelos. La cura del drift no puede tener drift adentro: pásale a tu caja de pigmentos la misma prueba de "¿cuántos valores distintos hay?" que le pasaste al storefront ad-hoc.
Atar una pieza a un primitivo "para ahorrarse el semántico". Qué pasa: para el Badge nuevo se escribe badge.bg → blue.600, saltándose color.primary. Por qué pasa: el semántico correspondiente parece un rodeo cuando el primitivo "ya es el azul que quiero". Cómo detectarlo: tienes tokens de componente que apuntan a primitivos (blue.600, gray.900) en vez de a roles. Cómo corregirlo: es el error de la L3, y en la paleta completa se ve su costo: el día del rebranding o del ajuste de tema oscuro, el Badge atado a blue.600 se queda fuera del cambio que sí alcanza a todo lo que pasa por color.primary. Cada pieza apunta al rol, no al pigmento —así el Badge viaja con la marca, no con un azul concreto—.
Creer que la paleta de color es "todo el sistema de tokens". Qué pasa: se arma el set de color de Mercado y se da por completo el trabajo de tokens. Por qué pasa: el color es lo que más se ve y lo que este módulo trabaja a fondo. Cómo detectarlo: tienes tokens de color impecables pero los espaciados, tamaños de letra y radios siguen a mano en los componentes. Cómo corregirlo: el color es una familia de tokens; faltan las demás. El espaciado y la tipografía, además, tienen estructura de escala —el módulo 4—, y los radios y sombras son familias propias. La paleta de esta lección es la parte del color, la más ilustrativa de las tres capas y el theming; el sistema completo suma las otras familias con el mismo mecanismo. No confundas "terminé el color" con "terminé los tokens".
Ejercicios
Ejercicio 1 — Ubica el error en la paleta. Un equipo propuso este set para Mercado. Encuentra los dos problemas (uno de nombrado, uno de capas) y corrígelos:
const tokens = {
primitives: { 'primary.600': '#2563eb', 'gray.900': '#111827', 'white': '#ffffff' },
semantics: { 'color.surface': { light: 'white', dark: 'gray.900' } },
component: { 'button.bg': 'gray.900' },
};
Ver solución
Problema 1 (nombrado, capa primitiva). El primitivo se llama primary.600 —un nombre de rol en la capa de valores crudos—. Los primitivos se nombran por familia y valor: debe ser blue.600 = #2563eb. El rol primary vive en un semántico (color.primary → blue.600), que aquí ni siquiera existe y debería.
Problema 2 (capas). El token de componente button.bg apunta directo a un primitivo (gray.900), saltándose la capa semántica —y encima a un gris, cuando un botón de marca debería usar el color primario—. Debe apuntar a un semántico: button.bg → color.primary.
Corregido:
const tokens = {
primitives: { 'blue.600': '#2563eb', 'gray.900': '#111827', 'white': '#ffffff' },
semantics: {
'color.primary': { light: 'blue.600', dark: 'blue.600' },
'color.surface': { light: 'white', dark: 'gray.900' },
},
component: { 'button.bg': 'color.primary' },
};
Los dos errores son los que las lecciones 3 y 5 anticiparon: rol en el primitivo, y pieza atada a primitivo. En un set real se cuelan justo así.
Ejercicio 2 — Predice el conteo y un valor. Sin correr nada, di qué números imprimiría el ejemplo en las tres líneas de "TIER N (...)" si agregamos un semántico color.muted y un token de componente searchbar.bg → color.muted (sin agregar primitivos). Y di a qué resolvería searchbar.bg en claro si color.muted es { light: 'gray.50', dark: 'gray.900' }.
Ver solución
Los conteos: primitivos siguen en 5 (no agregamos ninguno), semánticos pasan a 4 (agregamos color.muted), de componente pasan a 4 (agregamos searchbar.bg):
TIER 1 primitivos (5): ...
TIER 2 semanticos (4): ...
TIER 3 de componente (4): ...
Y resolveToken('searchbar.bg', 'light') sigue la cadena searchbar.bg → color.muted → gray.50 → #f9fafb, así que resuelve a #f9fafb. Fíjate en que no hizo falta un primitivo nuevo: color.muted reutiliza gray.50, que ya estaba en la caja. Agregar roles y piezas casi nunca requiere primitivos nuevos —la caja de pigmentos ya suele tener lo necesario—.
Ejercicio 3 — Diseña la paleta de un sub-producto. Mercado lanza "Mercado Pro" con color de marca morado (purple.600 = #7c3aed, purple.400 = #a78bfa) pero el mismo fondo y texto. Diseña el set de tokens de Mercado Pro reutilizando lo que se pueda, y di cuántos primitivos, semánticos y tokens de componente cambian respecto a la paleta base.
Ver solución
Solo cambia el color de marca; el fondo y el texto son idénticos. Así que se agregan dos primitivos morados y se redirige un semántico:
const tokensPro = {
primitives: {
'blue.600': '#2563eb', 'blue.400': '#60a5fa', // se quedan (por si acaso)
'purple.600': '#7c3aed', 'purple.400': '#a78bfa', // nuevos
'gray.50': '#f9fafb', 'gray.900': '#111827', 'white': '#ffffff',
},
semantics: {
'color.primary': { light: 'purple.600', dark: 'purple.400' }, // <- redirigido a morado
'color.surface': { light: 'white', dark: 'gray.900' }, // igual
'color.text': { light: 'gray.900', dark: 'gray.50' }, // igual
},
component: {
'button.bg': 'color.primary', // igual (hereda el nuevo morado solo)
'card.bg': 'color.surface', // igual
'card.text': 'color.text', // igual
},
};
Qué cambió respecto a la base: dos primitivos nuevos (los morados), un semántico redirigido (color.primary ahora apunta a morado), y cero tokens de componente. El button.bg no se tocó y sin embargo el botón de Mercado Pro es morado —porque referencia color.primary, y ese rol ahora apunta al morado—. Un sub-producto entero con otra marca cuesta redirigir un rol: la demostración más contundente de por qué las tres capas valen la pena. Esta es, además, la semilla de cómo un mismo sistema soporta varias marcas (multi-tenant), tema que la guía de Next.js retoma más adelante.
Resumen y siguiente paso
En esta lección viste el sistema de tokens de Mercado entero y funcionando: cinco primitivos (la caja de pigmentos), tres semánticos con override por tema (los roles), tres tokens de componente (las piezas del storefront), resueltos en claro y oscuro. Con la paleta terminada del pintor entendiste el momento: la última mirada al set completo antes de empezar a pintar. Ejecutaste la paleta y viste cada capa en su lugar —primitivos por valor, semánticos por rol con dos flechas cada uno, componentes que heredan— y la tabla final resolviendo seis valores coherentes con el theming, sin un solo crudo en un componente. Y viste el ahorro compuesto: agregar un Badge, un tema, o un sub-producto entero cuesta atar a un rol o redirigir un semántico, nunca inventar valores por pieza.
Antes de avanzar deberías poder: describir el set de Mercado por capas; explicar por qué cada token está en la capa que está; resolver un token de componente en ambos temas; y estimar qué cuesta agregar una pieza o un tema al sistema.
La lección 8 te saca del asiento del lector y te pone en el del constructor: el proyecto. Vas a definir tú el set de tokens de Mercado —primitivos, semánticos y de componente, para claro y oscuro—, expresarlo como CSS custom properties, y resolver varios tokens con resolveToken en ambos temas para probar que tu sistema es coherente. Todo lo que leíste en este módulo, hecho por tus manos, en un archivo que corre.
Recursos
- Design Tokens Community Group (W3C) — design-tokens.github.io/community-group. El estándar para describir un set completo de tokens como el de Mercado; la referencia formal del formato. En inglés.
- Material Design 3, "Design tokens" — m3.material.io/foundations/design-tokens/overview. Un set de tokens de producto real, con sus tres niveles y sus temas; el siguiente nivel de escala de lo que armaste aquí. En inglés.
- shadcn/ui, "Theming" — ui.shadcn.com/docs/theming. Una paleta de tokens semánticos (
background,foreground,primary, …) en custom properties con tema claro/oscuro; muy cercana a la de Mercado. En inglés. - Nathan Curtis, "Naming Tokens in Design Systems" — medium.com/eightshapes-llc/naming-tokens-in-design-systems-9e86c7444676. Cómo organizar y nombrar un set completo por niveles; útil al diseñar el tuyo en el proyecto. En inglés.