Sim, você pode descrever um tema em inglês e obter um design system funcional. O truque é que a descrição em inglês não é um prompt. É um arquivo fonte. E como qualquer arquivo fonte, precisa de um compiler, um sistema de tipos e testes.

Se você tratá-lo como uma query de chatbot, obterá uma paleta de cores diferente na terça-feira da que obteve na segunda-feira. Se você tratá-lo como uma DSL com um contexto delimitado e um schema estrito, obtém tokens reproduzíveis que seu design system pode consumir.

Design tokens são um problema de sincronização, não criativo

A maioria dos times não tem dificuldade em escolher cores. Eles têm dificuldade em manter as cores consistentes entre Figma, variáveis CSS e component libraries. Um workflow típico se parece com isto: um designer atualiza um código hex em um guia de estilos, um desenvolvedor o copia para um arquivo JSON, outro desenvolvedor referencia a chave errada em um componente React, e três meses depois você tem #1a1a2e em um lugar e #1b1b2f em outro.

A verdadeira dor é a entrega. Figma não é código. JSON não é uma ferramenta de design. O inglês fica no meio. É o único formato que tanto designers quanto desenvolvedores podem ler sem treinamento.

A pergunta não é se um LLM pode transformar inglês em códigos hex. A pergunta é se você pode transformar esse processo em algo confiável o suficiente para rodar em CI.

Como theme-to-code funciona na prática

A arquitetura é direta. Você escreve um manifest de tema curto em inglês simples. Um script o alimenta em um LLM com um formato de saída restrito. O LLM retorna tokens estruturados. Você escreve esses tokens no disco, os type-checks e os commiteia.

Aqui está o pipeline:

  1. O manifest: Um arquivo .txt ou .md que descreve o tema em prosa.
  2. O schema: Uma definição de Zod (ou JSON Schema) que o LLM deve retornar.
  3. O compiler: Um script que lê o manifest, chama o LLM com outputs estruturados e escreve um tokens.json.
  4. O gate: Um teste que falha se os tokens gerados se desviarem de um snapshot ou violarem constraints.

A chave é o passo três. Você não está pedindo ao modelo para “gerar um tema escuro.” Você está pedindo para ele preencher um schema. Isso transforma uma tarefa de escrita criativa em uma tarefa de extração de dados estruturados, na qual os LLMs são significativamente melhores.

Um compiler funcional em TypeScript

Aqui está uma implementação mínima mas completa. Ela lê uma descrição de tema, chama a OpenAI com um schema de Zod e escreve o resultado em um arquivo 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();

Você precisará de zod, openai e zod-to-json-schema como dependencies. O script assume que você está usando Bun, mas substituir Bun.file e Bun.write por fs.promises é trivial.

Os trade-offs que você não pode ignorar

Isso funciona, mas não é de graça.

Determinismo. Mesmo com temperature zero e outputs estruturados habilitados, a mesma descrição pode produzir códigos hex ligeiramente diferentes entre versões de modelo. Se você reexecutar o compiler após uma atualização da OpenAI, seus snapshot tests podem falhar. Você deve commitear o tokens.json gerado e só recompilar quando o manifest mudar.

Latência. Chamar um LLM em seu passo de build adiciona segundos, às vezes dezenas de segundos. Não compile temas a cada hot reload. Execute o compiler como um pre-commit hook ou em CI quando o arquivo manifest mudar.

Rigidez do schema. O LLM não pode inventar novas categorias de tokens. Se seu design system precisar de elevation.shadows.xl mais tarde, você deve atualizar o schema de Zod, o system prompt e o compiler antes de o modelo poder emiti-lo. Isso é uma feature, não um bug. Mantém a DSL delimitada.

Acessibilidade. Um LLM não sabe se sua cor primary contra seu background atende aos ratios de contraste WCAG. Você deve validar os tokens gerados com uma verificação separada. Adicione isso após a chamada 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`);
}

Quando isso desmorona: o problema da interação

Essa abordagem funciona maravilhosamente para paletas de cores, spacing scales e famílias tipográficas. Ela desmorona quando tokens interagem de formas difíceis de descrever em prosa.

Semantic tokens são a armadilha clássica. Você poderia escrever “use a cor primary para links e buttons.” Mas e os buttons desabilitados? E os links dentro de um banner de aviso que já usa a cor primary? Essas não são decisões a nível de tema. São regras a nível de componente, e pertencem à sua component library, não a um arquivo manifest.

Mantenha o manifest limitado a primitive tokens. Deixe seus components lidarem com a semântica.

Perguntas frequentes

O que é uma DSL de contexto delimitado? Uma linguagem pequena com um escopo estreito. Neste caso, a “linguagem” é prosa em inglês que descreve apenas design tokens, governada por um schema estrito.

A mesma descrição sempre produzirá os mesmos tokens? Nem sempre. Os outputs de LLM variam entre versões de modelo e providers. Commiteie os tokens gerados e use snapshot tests para detectar drift.

Posso usar isso para estilos a nível de componente? Não. O manifest deve definir apenas primitive tokens como cores e spacing. A semântica de components pertence à sua component library.

Preciso da OpenAI, ou outros modelos funcionarão? Qualquer modelo que suporte output JSON estruturado e siga um system prompt funcionará. Modelos locais como Llama 3.3 são viáveis se você tiver o hardware para executá-los rápido o suficiente para sua build pipeline.

Comece com um manifest de dez linhas e um schema estrito

Escolha um contexto delimitado, como uma landing page de marketing ou um admin dashboard. Escreva um manifest de dez linhas, defina um schema de Zod sem campos opcionais e gere seu primeiro arquivo de tokens. Commiteie o output. Escreva um teste que recompile o manifest e o compare ao snapshot commiteado.

Se o snapshot driftar sem que o manifest tenha mudado, seu compiler não é determinístico. Corrija o system prompt ou fixe a versão do modelo até que seja.

Seu design system não precisa de outro plugin do Figma. Precisa de uma fonte da verdade que tanto designers quanto desenvolvedores possam ler. Inglês simples é legível. O schema o torna confiável.