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

  1. Caos en las capas de tokens: Límites poco claros entre capas globales/semánticas/de componente, cadenas de referencia excesivamente largas
  2. Rendimiento del cambio de tema: El cambio de tema en tiempo de ejecución causa parpadeo de repintado
  3. Extensión multi-marca: Grandes diferencias de tokens entre marcas, herencia compleja
  4. Salida de compilación: Formatos de salida variables entre plataformas (CSS/JS/Swift/Kotlin)
  5. 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

  1. Integración con Token Studio: Plugin de Figma para sincronización bidireccional de tokens diseño-código
  2. Versionado de Tokens: Versionado semántico + generación automática de changelog
  3. Tokens para Tests A/B: Cambio dinámico de tokens en tiempo de ejecución para tests A/B visuales
  4. Token Lint: Plugin ESLint para detectar valores de diseño codificados en el código
  5. 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

Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →

#Design Token#设计系统#主题切换#CSS变量#2026#前端工程