Sistema de Design Tokens en la Práctica: 5 Patrones para Arquitectura Escalable de Temas y Multi-Marca
Design Tokens: La Infraestructura Atómica de los Sistemas de Diseño
El mayor punto de dolor en los sistemas de diseño no es la falta de componentes—es el cambio de tema mediante sobrescrituras, las extensiones de marca mediante duplicación y el modo oscuro mediante parches. Los Design Tokens abstraen decisiones de diseño como colores, espaciado y tipografía en pares clave-valor independientes de la plataforma, permitiendo "definir una vez, consumir en todas partes". En 2026, la especificación W3C Design Token ha alcanzado la Recomendación Candidata, Style Dictionary 4.0+ soporta salida multi-plataforma, y los Design Tokens se han convertido en la infraestructura estándar para los sistemas de diseño.
Este artículo recorre 5 patrones centrales, cubriendo el pipeline completo desde capas de Tokens → cambio de tema → multi-marca → pipeline de compilación → dinámicas en tiempo de ejecución.
Conceptos Centrales
| Concepto | Descripción |
|---|---|
| Design Token | Representación atómica de una decisión de diseño como un par clave-valor |
| Token Global | Valores globales brutos, ej., blue-500: #3b82f6 |
| Token Alias | Alias semántico, ej., primary: {blue-500} |
| Token de Componente | Token a nivel de componente, ej., button-primary-bg: {primary} |
| Tema | Una colección de valores de tokens, ej., tema claro/oscuro |
| Multi-Marca | Un mismo sistema de diseño soportando múltiples conjuntos de tokens de marca |
| Style Dictionary | Herramienta de compilación de tokens con salida multi-plataforma |
| W3C DTCG | Especificación del Design Token Community Group |
Análisis del Problema: 5 Desafíos en los Sistemas de Design Tokens
- Caos en las capas de tokens: Límites poco claros entre capas globales/semánticas/de componente, cadenas de referencia excesivamente largas
- Rendimiento del cambio de tema: El cambio de tema en tiempo de ejecución causa parpadeo de repintado
- Extensión multi-marca: Grandes diferencias de tokens entre marcas, herencia compleja
- Salida de compilación: Formatos de salida variables entre plataformas (CSS/JS/Swift/Kotlin)
- Colaboración de diseño: Los diseñadores usan Figma, los desarrolladores usan código—la sincronización de tokens es difícil
Paso a Paso: 5 Patrones de Design Tokens
Patrón 1: Arquitectura de Tokens de Tres Capas
// tokens/global/color.json
{
"color": {
"blue": {
"100": { "value": "#dbeafe", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
},
"gray": {
"50": { "value": "#f9fafb", "type": "color" },
"900": { "value": "#111827", "type": "color" }
},
"red": {
"500": { "value": "#ef4444", "type": "color" }
},
"green": {
"500": { "value": "#22c55e", "type": "color" }
}
}
}
// tokens/semantic/color.json
{
"color": {
"primary": { "value": "{color.blue.500}", "type": "color" },
"primary-hover": { "value": "{color.blue.600}", "type": "color" },
"background": { "value": "{color.gray.50}", "type": "color" },
"surface": { "value": "#ffffff", "type": "color" },
"text": { "value": "{color.gray.900}", "type": "color" },
"text-secondary": { "value": "{color.gray.500}", "type": "color" },
"error": { "value": "{color.red.500}", "type": "color" },
"success": { "value": "{color.green.500}", "type": "color" },
"border": { "value": "{color.gray.200}", "type": "color" }
}
}
// tokens/component/button.json
{
"button": {
"primary": {
"bg": { "value": "{color.primary}", "type": "color" },
"bg-hover": { "value": "{color.primary-hover}", "type": "color" },
"text": { "value": "#ffffff", "type": "color" },
"border-radius": { "value": "{radius.md}", "type": "borderRadius" },
"padding-x": { "value": "{spacing.4}", "type": "spacing" },
"padding-y": { "value": "{spacing.2}", "type": "spacing" }
},
"secondary": {
"bg": { "value": "{color.surface}", "type": "color" },
"text": { "value": "{color.primary}", "type": "color" },
"border": { "value": "{color.border}", "type": "color" }
}
}
}
Patrón 2: Cambio de Tema (Claro/Oscuro)
// tokens/semantic/color.dark.json
{
"color": {
"primary": { "value": "{color.blue.400}", "type": "color" },
"background": { "value": "{color.gray.900}", "type": "color" },
"surface": { "value": "#1f2937", "type": "color" },
"text": { "value": "{color.gray.50}", "type": "color" },
"text-secondary": { "value": "{color.gray.400}", "type": "color" },
"border": { "value": "{color.gray.700}", "type": "color" }
}
}
/* Salida de compilación: tokens.css */
:root {
--color-primary: #3b82f6;
--color-background: #f9fafb;
--color-surface: #ffffff;
--color-text: #111827;
--color-border: #e5e7eb;
--button-primary-bg: var(--color-primary);
--button-primary-text: #ffffff;
}
[data-theme="dark"] {
--color-primary: #60a5fa;
--color-background: #111827;
--color-surface: #1f2937;
--color-text: #f9fafb;
--color-border: #374151;
--button-primary-bg: var(--color-primary);
}
// Cambiador de tema
type Theme = 'light' | 'dark' | 'system';
class ThemeManager {
private mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
private listeners: Set<(theme: Theme) => void> = new Set();
init() {
const saved = localStorage.getItem('theme') as Theme | null;
if (saved && saved !== 'system') {
this.apply(saved);
} else {
this.apply(this.mediaQuery.matches ? 'dark' : 'light');
}
this.mediaQuery.addEventListener('change', (e) => {
if (this.getStored() === 'system') {
this.apply(e.matches ? 'dark' : 'light');
}
});
}
setTheme(theme: Theme) {
localStorage.setItem('theme', theme);
if (theme === 'system') {
this.apply(this.mediaQuery.matches ? 'dark' : 'light');
} else {
this.apply(theme);
}
this.listeners.forEach((fn) => fn(theme));
}
private apply(mode: 'light' | 'dark') {
document.documentElement.setAttribute('data-theme', mode);
}
private getStored(): Theme {
return (localStorage.getItem('theme') as Theme) || 'system';
}
onChange(fn: (theme: Theme) => void) {
this.listeners.add(fn);
return () => this.listeners.delete(fn);
}
}
Patrón 3: Herencia de Tokens Multi-Marca
// brands/brand-a/tokens.json
{
"color": {
"primary": { "value": "#3b82f6", "type": "color", "$extensions": { "brand": "A" } },
"primary-hover": { "value": "#2563eb", "type": "color" }
},
"typography": {
"font-family": { "value": "Inter, sans-serif", "type": "fontFamily" },
"heading-weight": { "value": "700", "type": "fontWeight" }
},
"radius": {
"base": { "value": "8px", "type": "borderRadius" }
}
}
// brands/brand-b/tokens.json
{
"color": {
"primary": { "value": "#8b5cf6", "type": "color", "$extensions": { "brand": "B" } },
"primary-hover": { "value": "#7c3aed", "type": "color" }
},
"typography": {
"font-family": { "value": "Poppins, sans-serif", "type": "fontFamily" },
"heading-weight": { "value": "600", "type": "fontWeight" }
},
"radius": {
"base": { "value": "4px", "type": "borderRadius" }
}
}
// Cambiador de marca
type Brand = 'brand-a' | 'brand-b';
async function switchBrand(brand: Brand) {
const brandCSS = await fetch(`/tokens/${brand}.css`).then((r) => r.text());
let style = document.getElementById('brand-tokens') as HTMLStyleElement;
if (!style) {
style = document.createElement('style');
style.id = 'brand-tokens';
document.head.appendChild(style);
}
style.textContent = brandCSS;
document.documentElement.setAttribute('data-brand', brand);
localStorage.setItem('brand', brand);
}
Patrón 4: Pipeline de Compilación de Style Dictionary
// style-dictionary.config.mjs
import StyleDictionary from 'style-dictionary';
StyleDictionary.registerTransform({
name: 'size/pxToRem',
type: 'value',
matcher: (token) => token.type === 'spacing' || token.type === 'fontSize',
transformer: (token) => {
const base = 16;
const val = parseFloat(token.value);
return `${val / base}rem`;
},
});
StyleDictionary.registerTransformGroup({
name: 'custom/css',
transforms: [
'attribute/cti',
'name/kebab',
'time/seconds',
'size/pxToRem',
'color/css',
],
});
const config = {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'custom/css',
buildPath: 'dist/css/',
files: [
{
destination: 'tokens.css',
format: 'css/variables',
options: { outputReferences: true },
},
],
},
js: {
transformGroup: 'js',
buildPath: 'dist/js/',
files: [
{
destination: 'tokens.js',
format: 'javascript/es6',
},
{
destination: 'tokens.d.ts',
format: 'typescript/es6-declarations',
},
],
},
json: {
transformGroup: 'json',
buildPath: 'dist/json/',
files: [
{
destination: 'tokens.json',
format: 'json',
},
],
},
ios: {
transformGroup: 'ios-swift-separate',
buildPath: 'dist/ios/',
files: [
{
destination: 'DesignTokens.swift',
format: 'ios-swift/class.swift',
className: 'DesignTokens',
},
],
},
android: {
transformGroup: 'android',
buildPath: 'dist/android/',
files: [
{
destination: 'design_tokens.xml',
format: 'android/resources',
},
],
},
},
};
export default config;
# Compilar todas las plataformas
npx style-dictionary build
# Observar cambios en tokens
npx style-dictionary build --watch
Patrón 5: Tokens Dinámicos en Tiempo de Ejecución
// runtime-token-engine.ts
interface TokenValue {
value: string;
type: string;
description?: string;
}
class RuntimeTokenEngine {
private tokens: Map<string, TokenValue> = new Map();
private subscribers: Map<string, Set<(value: string) => void>> = new Map();
loadFromCSS() {
const styles = getComputedStyle(document.documentElement);
const allVars = Array.from(document.styleSheets)
.flatMap((sheet) => {
try {
return Array.from(sheet.cssRules);
} catch {
return [];
}
})
.filter((rule) => rule instanceof CSSStyleRule)
.flatMap((rule: CSSStyleRule) => {
const props: string[] = [];
for (let i = 0; i < rule.style.length; i++) {
const prop = rule.style[i];
if (prop.startsWith('--')) {
props.push(prop);
}
}
return props;
});
const uniqueVars = [...new Set(allVars)];
uniqueVars.forEach((v) => {
const val = styles.getPropertyValue(v).trim();
if (val) {
this.tokens.set(v, { value: val, type: 'unknown' });
}
});
}
setToken(name: string, value: string) {
const varName = name.startsWith('--') ? name : `--${name}`;
document.documentElement.style.setProperty(varName, value);
this.tokens.set(varName, { value, type: 'unknown' });
this.notify(varName, value);
}
getToken(name: string): string | undefined {
const varName = name.startsWith('--') ? name : `--${name}`;
return getComputedStyle(document.documentElement)
.getPropertyValue(varName)
.trim();
}
subscribe(name: string, callback: (value: string) => void) {
const varName = name.startsWith('--') ? name : `--${name}`;
if (!this.subscribers.has(varName)) {
this.subscribers.set(varName, new Set());
}
this.subscribers.get(varName)!.add(callback);
return () => this.subscribers.get(varName)?.delete(callback);
}
private notify(name: string, value: string) {
this.subscribers.get(name)?.forEach((fn) => fn(value));
}
}
Guía de Trampas
Trampa 1: Cadenas de Referencia de Tokens Demasiado Profundas
// ❌ Incorrecto: cadena de referencia de 5 capas, difícil de depurar
{
"btn-bg": { "value": "{button.primary.bg}" },
"button.primary.bg": { "value": "{color.primary}" },
"color.primary": { "value": "{blue.500}" }
}
// ✅ Correcto: Mantener dentro de 3 capas
{
"button-primary-bg": { "value": "{color.primary}" },
"color.primary": { "value": "#3b82f6" }
}
Trampa 2: Valores de Color Codificados
/* ❌ Incorrecto: Colores codificados */
.card { background: #ffffff; border: 1px solid #e5e7eb; }
/* ✅ Correcto: Usar variables de tokens */
.card { background: var(--color-surface); border: 1px solid var(--color-border); }
Trampa 3: Parpadeo al Cambiar de Tema
<!-- ❌ Incorrecto: Tema establecido después de que JS carga, causando parpadeo -->
<!-- ✅ Correcto: Script inline bloquea el renderizado -->
<script>
(function() {
const theme = localStorage.getItem('theme');
if (theme === 'dark' || (!theme && matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
Trampa 4: Uso Excesivo de Tokens de Componente
// ❌ Incorrecto: Crear un token para cada propiedad del componente
{
"card-padding-top": { "value": "{spacing.4}" },
"card-padding-right": { "value": "{spacing.4}" },
"card-padding-bottom": { "value": "{spacing.4}" },
"card-padding-left": { "value": "{spacing.4}" }
}
// ✅ Correcto: Reutilizar tokens semánticos
{
"card-padding": { "value": "{spacing.4}" }
}
Trampa 5: Ignorar la Validación de Tipos de Tokens
// ❌ Incorrecto: Sin validación de tipos, un token de color recibe un valor de espaciado
// ✅ Correcto: Validar en tiempo de compilación
StyleDictionary.registerFilter({
name: 'isValidColor',
matcher: (token) => {
if (token.type === 'color') {
return /^#([0-9a-f]{3}){1,2}$/i.test(token.value) ||
token.value.startsWith('{');
}
return true;
},
});
Solución de Errores
| # | Mensaje de Error | Causa | Solución |
|---|---|---|---|
| 1 | Reference not found: {color.xxx} |
El token referencia una clave inexistente | Verificar ortografía de la ruta de referencia |
| 2 | Circular reference detected |
Referencia circular de tokens | Romper la cadena de referencia circular |
| 3 | Invalid token value |
Formato de valor de token inválido | Validar formato de color/espaciado |
| 4 | CSS variable not applied |
Especificidad del selector sobrescrita | Aumentar la especificidad del selector de variable CSS |
| 5 | Theme flash on load |
Inicialización del tema después del renderizado | Script inline bloqueante para establecer el tema |
| 6 | Style Dictionary build failed |
Error de formato en archivo de tokens | Verificar sintaxis JSON y $extensions |
| 7 | Token not found in output |
Token no coincidido por la fuente | Verificar ruta glob de source |
| 8 | Platform output mismatch |
Reglas de transformación inconsistentes | Unificar configuración de transformGroup |
| 9 | Figma sync conflict |
Tokens de Figma desincronizados con el código | Usar Token Studio para sincronización bidireccional |
| 10 | Runtime token override fails |
Sobrescritura en tiempo de ejecución sin efecto | Verificar prioridad y alcance de la variable CSS |
Optimización Avanzada
- Integración con Token Studio: Plugin de Figma para sincronización bidireccional de tokens diseño-código
- Versionado de Tokens: Versionado semántico + generación automática de changelog
- Tokens para Tests A/B: Cambio dinámico de tokens en tiempo de ejecución para tests A/B visuales
- Token Lint: Plugin ESLint para detectar valores de diseño codificados en el código
- Sitio de Documentación de Tokens: Documentación visual de tokens generada automáticamente
Comparación
| Dimensión | Variables CSS | Design Tokens | Configuración Tailwind | Tema CSS-in-JS |
|---|---|---|---|---|
| Soporte de Plataforma | Solo Web | Web/iOS/Android | Solo Web | Solo Web |
| Seguridad de Tipos | ❌ | ✅ | ✅ | ✅ |
| Cambio de Tema | ✅ | ✅ | ✅ | ✅ |
| Multi-Marca | Manual | Soporte nativo | Cambio de configuración | Cambio de configuración |
| Colaboración de Diseño | ❌ | ✅ Sincronización Figma | ❌ | ❌ |
| Salida de Compilación | CSS | CSS/JS/Swift/Kotlin | CSS/JS | JS |
Resumen: Los sistemas de Design Tokens son la infraestructura clave para evolucionar los sistemas de diseño de "bibliotecas de componentes" a "lenguajes de diseño". La arquitectura de tokens de tres capas (global → semántico → componente) combinada con la salida multi-plataforma de Style Dictionary establece una única fuente de verdad para las decisiones de diseño. En 2026, la especificación W3C DTCG en maduración impulsará la estandarización de tokens, haciendo que los tokens multi-marca y dinámicos en tiempo de ejecución sean características estándar para los sistemas de diseño empresariales.
Herramientas en Línea Recomendadas
- Formateador JSON: /es-419/json/format
- Calculadora de Hash: /es-419/encode/hash
- cURL a Código: /es-419/dev/curl-to-code
Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →