Ja, du kannst ein Theme auf Englisch beschreiben und ein funktionierendes Design-System erhalten. Der Haken ist, dass die englische Beschreibung kein Prompt ist. Sie ist eine Quelldatei. Und wie jede Quelldatei braucht sie einen Compiler, ein Typsystem und Tests.

Wenn du sie wie eine Chatbot-Anfrage behandelst, bekommst du am Dienstag eine andere Farbpalette als am Montag. Wenn du sie wie eine DSL mit begrenztem Kontext und striktem Schema behandelst, erhältst du reproduzierbare Tokens, die dein Design-System verarbeiten kann.

Design-Tokens sind ein Synchronisationsproblem, kein kreatives

Die meisten Teams haben keine Schwierigkeiten, Farben auszuwählen. Sie haben Schwierigkeiten, die Farben zwischen Figma, CSS-Variablen und Component Libraries konsistent zu halten. Ein typischer Workflow sieht so aus: Ein Designer aktualisiert einen Hex-Code im Style Guide, ein Entwickler kopiert ihn in eine JSON-Datei, ein anderer Entwickler referenziert den falschen Schlüssel in einer React-Komponente, und drei Monate später hast du an einer Stelle #1a1a2e und an einer anderen #1b1b2f.

Der echte Schmerz ist die Übergabe. Figma ist kein Code. JSON ist kein Design-Tool. Englisch sitzt in der Mitte. Es ist das einzige Format, das sowohl Designer als auch Entwickler ohne Schulung lesen können.

Die Frage ist nicht, ob ein LLM Englisch in Hex-Codes umwandeln kann. Die Frage ist, ob du diesen Prozess so zuverlässig machen kannst, dass er in CI läuft.

Wie Theme-to-Code tatsächlich funktioniert

Die Architektur ist geradlinig. Du schreibst ein kurzes Theme-Manifest in einfachem Englisch. Ein Script füttert es mit einem eingeschränkten Ausgabeformat an ein LLM. Das LLM gibt strukturierte Tokens zurück. Du schreibst diese Tokens auf die Festplatte, type-checkst sie und committed sie.

Hier ist die Pipeline:

  1. Das Manifest: Eine .txt- oder .md-Datei, die das Theme in Prosa beschreibt.
  2. Das Schema: Eine Zod- (oder JSON-Schema-) Definition, die das LLM zurückgeben muss.
  3. Der Compiler: Ein Script, das das Manifest liest, das LLM mit strukturierten Outputs aufruft und eine tokens.json schreibt.
  4. Das Gate: Ein Test, der fehlschlägt, wenn die generierten Tokens von einem Snapshot abweichen oder Constraints verletzen.

Der Schlüssel ist Schritt drei. Du fragst das Modell nicht, “generiere ein Dark Theme.” Du fragst es, ein Schema zu befüllen. Das verwandelt eine kreative Schreibaufgabe in eine strukturierte Datenextraktionsaufgabe, in der LLMs deutlich besser sind.

Ein funktionierender Compiler in TypeScript

Hier ist eine minimale, aber vollständige Implementierung. Sie liest eine Theme-Beschreibung, ruft OpenAI mit einem Zod-Schema auf und schreibt das Ergebnis in eine JSON-Datei.

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();

Du brauchst zod, openai und zod-to-json-schema als Dependencies. Das Script setzt voraus, dass du Bun verwendest, aber Bun.file und Bun.write durch fs.promises zu ersetzen ist trivial.

Die Trade-offs, die du nicht ignorieren kannst

Das funktioniert, aber es ist nicht umsonst.

Determinismus. Selbst bei Temperature Zero und aktivierten strukturierten Outputs kann dieselbe Beschreibung über Modellversionen hinweg leicht unterschiedliche Hex-Codes produzieren. Wenn du den Compiler nach einem OpenAI-Update neu ausführst, können deine Snapshot-Tests fehlschlagen. Du solltest die generierte tokens.json commiten und nur neu kompilieren, wenn sich das Manifest ändert.

Latenz. Einen LLM-Aufruf in deinen Build-Schritt einzubauen, fügt Sekunden, manchmal Zehnersekunden hinzu. Kompiliere Themes nicht bei jedem Hot Reload. Führe den Compiler als Pre-Commit-Hook oder in CI aus, wenn sich die Manifestdatei ändert.

Schema-Starrheit. Das LLM kann keine neuen Token-Kategorien erfinden. Wenn dein Design-System später elevation.shadows.xl braucht, musst du das Zod-Schema, den System-Prompt und den Compiler aktualisieren, bevor das Modell es ausgeben kann. Das ist ein Feature, kein Bug. Es hält die DSL begrenzt.

Barrierefreiheit. Ein LLM weiß nicht, ob deine Primary-Farbe vor deinem Hintergrund die WCAG-Kontrastverhältnisse erfüllt. Du musst die generierten Tokens mit einer separaten Prüfung validieren. Füge das nach dem TokenSchema.parse-Aufruf hinzu:

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`);
}

Wenn es scheitert: das Interaktionsproblem

Dieser Ansatz funktioniert wunderbar für Farbpaletten, Spacing-Scales und Typografie-Familien. Er fällt auseinander, wenn Tokens auf Weisen interagieren, die sich schwer in Prosa beschreiben lassen.

Semantic Tokens sind die klassische Falle. Du könntest schreiben: “Verwende die Primary-Farbe für Links und Buttons.” Aber was ist mit deaktivierten Buttons? Was ist mit Links innerhalb eines Warnbanners, das bereits die Primary-Farbe verwendet? Das sind keine Theme-Level-Entscheidungen. Das sind Component-Level-Regeln, und sie gehören in deine Component Library, nicht in eine Manifestdatei.

Halte das Manifest auf primitive Tokens begrenzt. Lass deine Components die Semantik handhaben.

Häufig gestellte Fragen

Was ist eine bounded-context DSL? Eine kleine Sprache mit engem Scope. In diesem Fall ist die “Sprache” englische Prosa, die nur Design-Tokens beschreibt, gesteuert von einem strikten Schema.

Wird dieselbe Beschreibung immer dieselben Tokens produzieren? Nicht immer. LLM-Outputs variieren über Modellversionen und Provider hinweg. Committe die generierten Tokens und verwende Snapshot-Tests, um Drift zu erkennen.

Kann ich das für Component-Level-Styles verwenden? Nein. Das Manifest sollte nur primitive Tokens wie Farben und Spacing definieren. Component-Semantik gehört in deine Component Library.

Brauche ich OpenAI, oder funktionieren andere Modelle? Jedes Modell, das strukturierte JSON-Outputs unterstützt und einem System-Prompt folgt, funktioniert. Lokale Modelle wie Llama 3.3 sind viable Optionen, wenn du die Hardware hast, sie schnell genug für deine Build-Pipeline laufen zu lassen.

Starte mit einem zehnzeiligen Manifest und einem strikten Schema

Wähle einen begrenzten Kontext, wie eine Marketing-Landing-Page oder ein Admin-Dashboard. Schreibe ein zehnzeiliges Manifest, definiere ein Zod-Schema ohne optionale Felder und generiere deine erste Token-Datei. Committe den Output. Schreibe einen Test, der das Manifest neu kompiliert und mit dem committeden Snapshot vergleicht.

Wenn der Snapshot driftet, ohne dass sich das Manifest geändert hat, ist dein Compiler nicht deterministisch. Korrigiere den System-Prompt oder pinne die Modellversion, bis er es ist.

Dein Design-System braucht kein weiteres Figma-Plugin. Es braucht eine Single Source of Truth, die sowohl Designer als auch Entwickler lesen können. Einfaches Englisch ist lesbar. Das Schema macht es zuverlässig.