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.