I locked my AI's design output with JSON. It worked perfectly — and everything looked like a spreadsheet

작성자

카테고리:

← 피드로
DEV Community · Mxhlix · 2026-08-04 개발(SW)

Mxhlix

Mxhlix

Posted on Aug 4 • Edited on Aug 5

Mass-produce web tools with an AI agent and you will hit this wall: the design drifts on every generation. Same instructions, subtly different colors, spacing, and component shapes each time. Running 600+ tools in five locales, I went through three distinct eras of trying to lock the design down. Each era had a failure worth writing down.

Era 1: define the UI in JSON → it locked. And then

First attempt: define each tool’s UI as JSON and generate the page from it. Scope: simple calculator-type tools with no image preview. Labels, inputs, outputs as JSON values, rendered through a fixed pipeline.

As a locking mechanism, it worked. What’s in the JSON is what renders — generation-to-generation drift: gone.

But the screens came out lifeless. Straight lines everywhere, not a curve in sight. Every value sitting in its own little cell. To put it bluntly: it looked like a spreadsheet.

In hindsight the cause is structural. In a JSON-definition scheme, the expressive ceiling is whatever the renderer implements. You can add rounded corners and gradients and exception paddings to the schema — but that means endlessly fattening the schema and the renderer, and the limit of that road is reinventing HTML and CSS.

The mechanism that killed the drift killed the expressiveness with it.

Update (Aug 2026): the actual Era 1 JSON, and the screen it produced

A commenter asked to see the actual Era 1 JSON — how exactly the structure/renderer split worked before the token-only locking. I dug the files out of the January 2026 commits.

First, the screen. This is the Era 1 BMI calculator, re-rendered today from the code exactly as it existed then (English locale, 170 cm / 65 kg entered):

Era 1 BMI calculator: white card, stacked plain inputs, one flat button, result box
This is the screen I meant by “looked like a spreadsheet.”

The entire UI definition for that tool was this JSON — the real catalog entry (ja/es locales omitted):

{
  "slug": "bmi-calculator",
  "template_id": "calc/bmi_calculator.html",
  "category": "calc",
  "locales": {
    "en": {
      "name": "BMI Calculator",
      "desc": "Calculate Body Mass Index.",
      "headline": "BMI Calculator",
      "label_height": "Height",
      "label_weight": "Weight",
      "btn_calc": "Calculate",
      "result_prefix": "BMI:"
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

That’s it. Labels and strings. No colors, no spacing, no layout. Structure, styling, and the calculation logic all lived in the fixed 53-line template that template_id points to — the JSON values just get poured in:

<div>
    <label class="block text-sm font-medium mb-2 ...">{{ t.label_height }}</label>
    <input type="number" id="height" placeholder="cm" class="w-full p-2 border rounded ...">
</div>
<button onclick="calculateBMI()" class="w-full bg-indigo-600 ...">
    {{ t.btn_calc }}
</button>

Enter fullscreen mode Exit fullscreen mode

The AI could only ever fill in JSON values, so drift was structurally impossible. And the screen could never express anything the template didn’t already implement. The one-value-per-cell look in the screenshot is the direct consequence of that split.

Era 2: lock design tokens, not layout

Change of policy: stop locking structure (layout); lock only identity (the system of color, typography, radii, spacing).

Concretely: a CSS variables file becomes the canonical source, and every tool uses only those variables (palette, fonts, radii, shadows). Alongside it, a living UI kit — a reference implementation you can actually open in a browser. “This is what this project’s card/button/input looks like,” expressed as running HTML rather than a spec. The instruction to the AI changes from “render exactly this JSON” to “use this kit’s parts and variables; compose the layout freely.”

The result: layout optimizes per tool while the brand stays coherent. It’s Era 1 with the locking granularity inverted. Era 1 locked everything and expression died. Era 2 locks tokens only and composition is free.

One tailwind helped: the models’ own design ability kept improving with each release. “Compose freely” only works when the compositions come back good. Era 1’s premise — “free composition means drift” — was true at the time, and then time dissolved the premise.

Era 3 (now): ride the official rails where they exist

Today, skill mechanisms in Claude Code and official design-system integrations cover a growing share of “teach the AI your design conventions.” What’s left to self-build is shrinking to the genuinely project-specific parts: your tokens, your UI kit.

What transfers out of this

First: “it locked” is not the same as “it succeeded.” Evaluate the locking mechanism including its side effects. Era 1 achieved its goal and failed.

Second: choose the granularity of what you lock. Lock down to layout and you get spreadsheets. Lock tokens only and brand coherence coexists with compositional freedom.

Third: put an expiry date on your premises. “Free composition means drift” was a fact, and then it wasn’t. When a mechanism’s premise collapses, throw the mechanism away.

Limits and caveats

Era 1’s JSON approach isn’t universally wrong. For outputs where structural uniformity is the value — reports, forms, genuinely spreadsheet-like artifacts — JSON definition is still the right answer. My case was web-tool UI, so it wasn’t.

This whole arc ran in parallel with 2025–2026 model improvements. The optimal locking granularity will keep moving as model capability does.

Timeframe: H1 2026 (the Era 1 JSON catalog still exists in a March 2026 backup). Environment: Claude Code / 600+ web tools × 5 locales.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다