Skip to content

Transmogrifier

Credit: This method was developed by Marcin Wichary.

A single-page HTML tool for rapidly exploring a complex design space. Toggle controls update a live preview instantly, so you can compare options and converge on a final design without manual editing or page reloads.

When to use

Use the transmogrifier pattern when you need to make multiple interdependent design decisions and want to explore the space systematically rather than converging prematurely on one option.

Core idea

Build a toggle control for each design decision. Each control sets a data-* attribute on a root element; CSS rules keyed to those attributes render the live preview. Flipping a toggle shows the effect immediately — no manual editing, no page reload.

Work through decisions methodically: identify every variable, enumerate the reasonable options for each, then eliminate clearly wrong values before settling on winners.

Phases (optional): When a design has many decisions, organize them into phases ordered from broad to fine — structure, layout, visual hierarchy, then surface. Work one phase at a time; don't touch surface details until structure is locked. Phase tabs in the controls panel enforce this discipline.

The pattern

State as data-* attributes

Store every design decision as a data-* attribute on a single root element (typically <body> or a wrapper <div>). CSS selectors read those attributes to style the preview:

html
<body data-layout="card" data-density="comfortable" data-radius="rounded">
  <div class="preview">…</div>
</body>
css
/* layout decision */
[data-layout="card"] .preview { display: flex; flex-direction: column; }
[data-layout="inline"] .preview { display: flex; flex-direction: row; }

/* density decision */
[data-density="comfortable"] .preview { padding: 24px; gap: 16px; }
[data-density="compact"] .preview { padding: 12px; gap: 8px; }

JavaScript only updates attributes — it never touches style directly:

js
function set(decision, value) {
  document.body.setAttribute(`data-${decision}`, value);
}

Controls

Render one control group per decision. Radio buttons or segmented controls work well; clicking immediately updates the root attribute:

html
<div class="control-group">
  <label>Layout</label>
  <div class="options">
    <button onclick="set('layout', 'card')" class="active">Card</button>
    <button onclick="set('layout', 'inline')">Inline</button>
  </div>
</div>

Phase tabs (optional)

For complex designs with many decisions, group controls into phase tabs ordered broad to fine. Show only the controls for the active phase — Surface controls stay hidden until you click the Surface tab:

html
<nav class="phases">
  <button class="active" onclick="showPhase('structure')">Structure</button>
  <button onclick="showPhase('layout')">Layout</button>
  <button onclick="showPhase('hierarchy')">Hierarchy</button>
  <button onclick="showPhase('surface')">Surface</button>
</nav>

"Show all" canvas

For any single decision, a "Show all" button renders every option side-by-side in a scrolling canvas. This gives a broad view of the decision space without manually toggling through each value:

js
// previewHTML is the markup for your component — either a constant or a function call
const previewHTML = renderComponentHTML();

function showAll(decision, values) {
  const canvas = document.getElementById('canvas');
  canvas.innerHTML = values.map(value => `
    <figure data-${decision}="${value}">
      ${previewHTML}
      <figcaption>${value}</figcaption>
    </figure>
  `).join('');
  canvas.hidden = false;
}

Each <figure> carries the decision's data-* attribute, so the same CSS rules that drive the live preview also render the canvas tiles.

Example

See transmogrifier.example.html — a full working transmogrifier for a notification card. 11 design decisions organized into four phases, with a "Show all" canvas.

Process

  1. Identify decisions — list every visual property that could vary (layout, spacing, shape, color, type, etc.)
  2. Enumerate options — for each decision, list all reasonable values
  3. Build controls — add a toggle for each decision
  4. Eliminate options — hide or disable values that are clearly wrong
  5. Select winners — mark the chosen value for each decision

Once every decision has a selected value the design is done. Optionally run user tests on specific combinations before locking choices.

With phases: For complex designs, group decisions into phases and work broad-to-fine — complete all decisions in one phase before moving to the next.