Sí, puedes describir un tema en inglés y obtener un design system funcional. El truco es que la descripción en inglés no es un prompt. Es un archivo fuente. Y como cualquier archivo fuente, necesita un compiler, un sistema de tipos y pruebas.

Si lo tratas como una query de chatbot, obtendrás una paleta de colores diferente el martes de la que obtuviste el lunes. Si lo tratas como un DSL con un contexto delimitado y un schema estricto, obtienes tokens reproducibles que tu design system puede consumir.

Los design tokens son un problema de sincronización, no creativo

La mayoría de los equipos no tienen dificultades para elegir colores. Tienen dificultades para mantener los colores consistentes entre Figma, variables CSS y component libraries. Un workflow típico se ve así: un diseñador actualiza un código hex en una guía de estilos, un desarrollador lo copia a un archivo JSON, otro desarrollador referencia la clave equivocada en un componente React, y tres meses después tienes #1a1a2e en un lugar y #1b1b2f en otro.

El verdadero dolor es la entrega. Figma no es código. JSON no es una herramienta de diseño. El inglés está en el medio. Es el único formato que tanto diseñadores como desarrolladores pueden leer sin entrenamiento.

La pregunta no es si un LLM puede convertir inglés en códigos hex. La pregunta es si puedes convertir ese proceso en algo lo suficientemente confiable para ejecutar en CI.

Cómo funciona theme-to-code en la práctica

La arquitectura es directa. Escribes un manifest de tema corto en inglés simple. Un script lo alimenta a un LLM con un formato de salida restringido. El LLM devuelve tokens estructurados. Escribes esos tokens en disco, los type-checks y los commiteas.

Aquí está el pipeline:

  1. El manifest: Un archivo .txt o .md que describe el tema en prosa.
  2. El schema: Una definición de Zod (o JSON Schema) que el LLM debe devolver.
  3. El compiler: Un script que lee el manifest, llama al LLM con outputs estructurados y escribe un tokens.json.
  4. La puerta: Una prueba que falla si los tokens generados se desvían de un snapshot o violan constraints.

La clave es el paso tres. No le estás pidiendo al modelo que “genere un tema oscuro.” Le estás pidiendo que complete un schema. Eso convierte una tarea de escritura creativa en una tarea de checkout de datos estructurados, en la cual los LLMs son significativamente mejores.

Un compiler funcional en TypeScript

Aquí está una implementación mínima pero completa. Lee una descripción de tema, llama a OpenAI con un schema de Zod y escribe el resultado en un archivo JSON.

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import OpenAI from "openai";

// 1. Define the bounded context: only these tokens exist
const TokenSchema = z.object({
  name: z.string(),
  colors: z.object({
    background: z.string().regex(/^#[0-9a-f]{6}$/i),
    surface: z.string().regex(/^#[0-9a-f]{6}$/i),
    primary: z.string().regex(/^#[0-9a-f]{6}$/i),
    text: z.string().regex(/^#[0-9a-f]{6}$/i),
    muted: z.string().regex(/^#[0-9a-f]{6}$/i),
  }),
  spacing: z.object({
    unit: z.number().min(4).max(16),
    scale: z.array(z.number()).length(4),
  }),
  radii: z.object({
    sm: z.number(),
    md: z.number(),
    lg: z.number(),
  }),
});

type TokenSet = z.infer<typeof TokenSchema>;

// 2. The compiler
async function compileTheme(
  description: string,
  apiKey: string
): Promise<TokenSet> {
  const client = new OpenAI({ apiKey });

  const completion = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content:
          "You are a design-token compiler. " +
          "Convert the user's theme description into the exact JSON schema provided. " +
          "All colors must be valid 6-digit hex. " +
          "The spacing unit must be a multiple of 4. " +
          "The scale array must have exactly 4 values.",
      },
      {
        role: "user",
        content: description,
      },
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "theme_tokens",
        strict: true,
        schema: zodToJsonSchema(TokenSchema),
      },
    },
  });

  const raw = JSON.parse(completion.choices[0].message.content!);
  return TokenSchema.parse(raw);
}

// 3. CLI entrypoint
async function main() {
  const description = await Bun.file("theme.manifest.txt").text();
  const tokens = await compileTheme(description, process.env.OPENAI_API_KEY!);
  await Bun.write("tokens.json", JSON.stringify(tokens, null, 2));
  console.log("Compiled theme:", tokens.name);
}

main();

Necesitarás zod, openai y zod-to-json-schema como dependencies. El script asume que usas Bun, pero reemplazar Bun.file y Bun.write por fs.promises es trivial.

Los trade-offs que no puedes ignorar

Esto funciona, pero no es gratis.

Determinismo. Incluso con temperature en cero y outputs estructurados habilitados, la misma descripción puede producir códigos hex ligeramente diferentes entre versiones de modelo. Si reejecutas el compiler después de una actualización de OpenAI, tus snapshot tests pueden fallar. Deberías commitear el tokens.json generado y solo recompilar cuando el manifest cambie.

Latencia. Llamar a un LLM en tu paso de build agrega segundos, a veces decenas de segundos. No compiles temas en cada hot reload. Ejecuta el compiler como un pre-commit hook o en CI cuando el archivo manifest cambie.

Rigidez del schema. El LLM no puede inventar nuevas categorías de tokens. Si tu design system necesita elevation.shadows.xl más adelante, debes actualizar el schema de Zod, el system prompt y el compiler antes de que el modelo pueda emitirlo. Eso es una feature, no un bug. Mantiene el DSL delimitado.

Accesibilidad. Un LLM no sabe si tu color primary contra tu background cumple con los ratios de contraste WCAG. Debes validar los tokens generados con una verificación separada. Añade esto después de la llamada a TokenSchema.parse:

function contrastRatio(hex1: string, hex2: string): number {
  // WCAG relative luminance calculation
  const lum = (hex: string) => {
    const rgb = parseInt(hex.slice(1), 16);
    const [r, g, b] = [(rgb >> 16) & 0xff, (rgb >> 8) & 0xff, rgb & 0xff];
    const f = (c: number) => {
      c /= 255;
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
    };
    return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
  };
  const l1 = lum(hex1) + 0.05;
  const l2 = lum(hex2) + 0.05;
  return l1 > l2 ? l1 / l2 : l2 / l1;
}

// After parsing tokens
const ratio = contrastRatio(tokens.colors.primary, tokens.colors.background);
if (ratio < 4.5) {
  throw new Error(`Contrast ratio ${ratio.toFixed(2)} fails WCAG AA`);
}

Cuándo se desmorona: el problema de la interacción

Este enfoque funciona de maravilla para paletas de colores, spacing scales y familias tipográficas. Se desmorona cuando los tokens interactúan de maneras difíciles de describir en prosa.

Los semantic tokens son la trampa clásica. Podrías escribir “usa el color primary para links y buttons.” ¿Pero qué pasa con los buttons deshabilitados? ¿Qué pasa con los links dentro de un banner de advertencia que ya usa el color primary? Estas no son decisiones a nivel de tema. Son reglas a nivel de componente, y pertenecen a tu component library, no a un archivo manifest.

Mantén el manifest limitado a primitive tokens. Deja que tus components manejen la semántica.

Preguntas frecuentes

¿Qué es un DSL de contexto delimitado? Un lenguaje pequeño con un scope estrecho. En este caso, el “lenguaje” es prosa en inglés que solo describe design tokens, gobernada por un schema estricto.

¿La misma descripción siempre producirá los mismos tokens? No siempre. Los outputs de LLM varían entre versiones de modelo y proveedores. Commitea los tokens generados y usa snapshot tests para detectar drift.

¿Puedo usar esto para estilos a nivel de componente? No. El manifest solo debe definir primitive tokens como colores y spacing. La semántica de components pertenece a tu component library.

¿Necesito OpenAI, o funcionarán otros modelos? Cualquier modelo que soporte output JSON estructurado y siga un system prompt funcionará. Modelos locales como Llama 3.3 son viables si tienes el hardware para ejecutarlos lo suficientemente rápido para tu build pipeline.

Empieza con un manifest de diez líneas y un schema estricto

Elige un contexto delimitado, como una landing page de marketing o un admin dashboard. Escribe un manifest de diez líneas, define un schema de Zod sin campos opcionales y genera tu primer archivo de tokens. Commitea el output. Escribe una prueba que recompile el manifest y lo compare contra el snapshot commiteado.

Si el snapshot drifta sin que el manifest haya cambiado, tu compiler no es determinista. Corrige el system prompt o fija la versión del modelo hasta que lo sea.

Tu design system no necesita otro plugin de Figma. Necesita una fuente de verdad que tanto diseñadores como desarrolladores puedan leer. El inglés simple es legible. El schema lo hace confiable.