Saltar al contenido

Design Tokens: Uniendo Diseño y Desarrollo

Cómo los design tokens crean una única fuente de verdad para colores, espaciado y tipografía, eliminando la divergencia entre Figma y producción.

5 min de lectura
Pipeline de design tokens fluyendo desde la herramienta de diseño a múltiples salidas de plataforma

Los sistemas de diseño fallan cuando los diseñadores actualizan un color en Figma y los desarrolladores nunca se enteran. O cuando los desarrolladores agregan un nuevo valor de espaciado que no existe en la especificación de diseño. Los design tokens resuelven esto estableciendo una única fuente de verdad: valores independientes de la plataforma que tanto las herramientas como el código consumen.

Un design token es un valor con nombre: color-primary-500: #3B82F6. Ese valor se transforma en propiedades personalizadas de CSS, configuración de Tailwind, constantes Swift para iOS y recursos XML de Android. Cambia el token y todas las plataformas se actualizan.

Cómo se ven los tokens

Los tokens normalmente se almacenan en JSON o YAML, siguiendo la emergente especificación de Design Tokens del W3C.

jsonjson
{
  "color": {
    "primary": {
      "100": { "$value": "#DBEAFE", "$type": "color" },
      "300": { "$value": "#93C5FD", "$type": "color" },
      "500": { "$value": "#3B82F6", "$type": "color" },
      "700": { "$value": "#1D4ED8", "$type": "color" },
      "900": { "$value": "#1E3A8A", "$type": "color" }
    },
    "neutral": {
      "50":  { "$value": "#F9FAFB", "$type": "color" },
      "200": { "$value": "#E5E7EB", "$type": "color" },
      "500": { "$value": "#6B7280", "$type": "color" },
      "800": { "$value": "#1F2937", "$type": "color" },
      "950": { "$value": "#030712", "$type": "color" }
    }
  },
  "spacing": {
    "xs":  { "$value": "4px",  "$type": "dimension" },
    "sm":  { "$value": "8px",  "$type": "dimension" },
    "md":  { "$value": "16px", "$type": "dimension" },
    "lg":  { "$value": "24px", "$type": "dimension" },
    "xl":  { "$value": "32px", "$type": "dimension" },
    "2xl": { "$value": "48px", "$type": "dimension" }
  },
  "font": {
    "family": {
      "sans":  { "$value": "Inter, system-ui, sans-serif", "$type": "fontFamily" },
      "mono":  { "$value": "JetBrains Mono, monospace", "$type": "fontFamily" }
    },
    "size": {
      "sm":   { "$value": "14px", "$type": "dimension" },
      "base": { "$value": "16px", "$type": "dimension" },
      "lg":   { "$value": "18px", "$type": "dimension" },
      "xl":   { "$value": "20px", "$type": "dimension" },
      "2xl":  { "$value": "24px", "$type": "dimension" },
      "4xl":  { "$value": "36px", "$type": "dimension" }
    }
  }
}

Este JSON es la fuente. Todo lo demás se genera a partir de él.

Transformación de tokens con Style Dictionary

Style Dictionary es la herramienta estándar para transformar tokens en salidas específicas de plataforma. Un archivo fuente produce CSS, JavaScript, TypeScript y formatos nativos móviles.

jsjavascript
// style-dictionary.config.js
const StyleDictionary = require('style-dictionary');
 
module.exports = {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: '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',
      }],
    },
    typescript: {
      transformGroup: 'js',
      buildPath: 'dist/ts/',
      files: [{
        destination: 'tokens.ts',
        format: 'javascript/es6',
      }],
    },
    tailwind: {
      transformGroup: 'js',
      buildPath: 'dist/tailwind/',
      files: [{
        destination: 'tailwind-tokens.js',
        format: 'javascript/module',
      }],
    },
  },
};

Ejecutar style-dictionary build produce estas salidas:

csscss
/* dist/css/tokens.css */
:root {
  --color-primary-100: #DBEAFE;
  --color-primary-300: #93C5FD;
  --color-primary-500: #3B82F6;
  --color-primary-700: #1D4ED8;
  --color-primary-900: #1E3A8A;
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --font-family-sans: Inter, system-ui, sans-serif;
  --font-size-base: 16px;
}
tstypescript
// dist/ts/tokens.ts
export const ColorPrimary100 = "#DBEAFE";
export const ColorPrimary500 = "#3B82F6";
export const SpacingMd = "16px";
export const FontFamilySans = "Inter, system-ui, sans-serif";

Capas de tokens: global, semántica y componente

Los valores crudos como #3B82F6 son tokens globales. Necesitan una capa semántica que describa la intención y una capa de componente que se asigne a elementos específicos de la UI.

jsonjson
{
  "color": {
    "global": {
      "blue-500": { "$value": "#3B82F6" },
      "red-500":  { "$value": "#EF4444" },
      "green-500": { "$value": "#22C55E" },
      "gray-800": { "$value": "#1F2937" },
      "gray-50":  { "$value": "#F9FAFB" }
    },
    "semantic": {
      "action": { "$value": "{color.global.blue-500}" },
      "error":  { "$value": "{color.global.red-500}" },
      "success": { "$value": "{color.global.green-500}" },
      "text-primary": { "$value": "{color.global.gray-800}" },
      "bg-primary":   { "$value": "{color.global.gray-50}" }
    },
    "component": {
      "button-bg":       { "$value": "{color.semantic.action}" },
      "button-bg-hover":  { "$value": "{color.global.blue-700}" },
      "alert-error-bg":  { "$value": "{color.semantic.error}" },
      "input-border":    { "$value": "{color.global.gray-300}" }
    }
  }
}
csscss
/* ❌ Usar tokens globales directamente en componentes */
.button {
  background: var(--color-global-blue-500);
  /* ¿Qué pasa cuando el color de marca cambia a morado? */
  /* Buscas blue-500 en todo el código */
}
 
/* ✅ Usar tokens semánticos/de componente */
.button {
  background: var(--color-button-bg);
  /* Cambio de marca = actualizar un mapeo de token */
}

El enfoque de tres capas significa que un cambio de marca requiere modificar solo los mapeos semánticos a globales. Sin cambios en el código de componentes, sin ediciones de archivos CSS.

Modo oscuro mediante alias de tokens

El modo oscuro se vuelve trivial cuando los tokens usan nombres semánticos. Intercambia los valores globales detrás de los tokens semánticos.

jsonjson
{
  "color": {
    "semantic": {
      "text-primary": {
        "$value": "{color.global.gray-900}",
        "$extensions": {
          "dark": "{color.global.gray-100}"
        }
      },
      "bg-primary": {
        "$value": "{color.global.white}",
        "$extensions": {
          "dark": "{color.global.gray-900}"
        }
      },
      "bg-surface": {
        "$value": "{color.global.gray-50}",
        "$extensions": {
          "dark": "{color.global.gray-800}"
        }
      }
    }
  }
}
csscss
/* CSS generado con soporte para modo oscuro */
:root {
  --color-text-primary: #111827;
  --color-bg-primary: #FFFFFF;
  --color-bg-surface: #F9FAFB;
}
 
@media (prefers-color-scheme: dark) {
  :root {
    --color-text-primary: #F3F4F6;
    --color-bg-primary: #111827;
    --color-bg-surface: #1F2937;
  }
}
 
/* O alternancia basada en clases */
[data-theme="dark"] {
  --color-text-primary: #F3F4F6;
  --color-bg-primary: #111827;
  --color-bg-surface: #1F2937;
}

Cada componente que usa var(--color-text-primary) se adapta automáticamente al modo oscuro. Sin CSS condicional, sin hojas de estilo específicas por tema, sin alternancia con JavaScript.

Integración de tokens con Tailwind CSS

Los proyectos de Tailwind pueden consumir design tokens generando la configuración de Tailwind a partir de los archivos de tokens.

jsjavascript
// tailwind.config.js
const tokens = require('./dist/tailwind/tailwind-tokens');
 
module.exports = {
  theme: {
    colors: {
      primary: {
        100: tokens.ColorPrimary100,
        300: tokens.ColorPrimary300,
        500: tokens.ColorPrimary500,
        700: tokens.ColorPrimary700,
        900: tokens.ColorPrimary900,
      },
      neutral: {
        50:  tokens.ColorNeutral50,
        200: tokens.ColorNeutral200,
        500: tokens.ColorNeutral500,
        800: tokens.ColorNeutral800,
      },
    },
    spacing: {
      xs: tokens.SpacingXs,
      sm: tokens.SpacingSm,
      md: tokens.SpacingMd,
      lg: tokens.SpacingLg,
      xl: tokens.SpacingXl,
    },
    fontFamily: {
      sans: [tokens.FontFamilySans],
      mono: [tokens.FontFamilyMono],
    },
  },
};
tsxtsx
// Components use Tailwind classes backed by tokens
function Card({ title, children }: CardProps) {
  return (
    <div className="bg-neutral-50 rounded-lg p-lg shadow-sm">
      <h3 className="text-primary-900 font-sans text-xl mb-sm">
        {title}
      </h3>
      <div className="text-neutral-800">{children}</div>
    </div>
  );
}

Ahora bg-neutral-50 se resuelve al valor del token, no a un color codificado de forma rígida. Cambiar la paleta neutral actualiza la salida de Tailwind automáticamente.

El pipeline de tokens en CI

Los tokens deben construirse y validarse en CI antes de hacer merge. Un archivo de tokens roto debería fallar el build de la misma forma que lo haría un componente roto.

ymlyaml
# .github/workflows/tokens.yml
name: Build Design Tokens
on:
  pull_request:
    paths:
      - 'tokens/**'
      - 'style-dictionary.config.js'
 
jobs:
  build-tokens:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
 
      - name: Instalar dependencias
        run: npm ci
 
      - name: Validar esquema de tokens
        run: npx ajv validate -s tokens/schema.json -d "tokens/**/*.json"
 
      - name: Construir tokens
        run: npx style-dictionary build
 
      - name: Verificar cambios no confirmados
        run: |
          git diff --exit-code dist/
          echo "Las salidas de tokens están actualizadas"
shbash
# Hook de pre-commit — reconstruir tokens cuando cambia la fuente
#!/bin/sh
# .husky/pre-commit
CHANGED=$(git diff --cached --name-only -- 'tokens/')
if [ -n "$CHANGED" ]; then
  npx style-dictionary build
  git add dist/
fi

Esto garantiza que las salidas de tokens en dist/ siempre coincidan con los archivos fuente. Si un desarrollador cambia un token pero olvida reconstruir, CI lo detecta.

Conclusiones clave

  1. Los tokens son la única fuente de verdad — un archivo JSON genera CSS, JS, Tailwind y formatos nativos móviles
  2. Usa tres capas: global, semántica y componente — nunca referencies valores de color crudos en el código de componentes
  3. El modo oscuro es un intercambio de tokens — los tokens semánticos hacen que los temas sean automáticos sin cambios en componentes
  4. Style Dictionary maneja la transformación — defínelo una vez, genera para todas partes
  5. Valida los tokens en CI — los tokens rotos rompen la UI; trátalos como código
  6. Alimenta la configuración de Tailwind con tokens — estilos basados en clases respaldados por valores del sistema de diseño en lugar de números arbitrarios
Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX