Use this when a reader needs to tune a few inputs and see the result update live — here, controls on the left assemble an LLM prompt that re-renders in the preview on the right as you change them.
Copyable exemplar. Lift the <section> blocks below into a lesson built from assets/lesson-template.html — keep the design tokens and the .tech-toggle pattern verbatim.
A prompt is just the instruction you hand to an AI. Small wording changes — a friendlier tone, a shorter length, naming who the audience is — quietly steer what comes back. A tuner turns those choices into knobs: you nudge a control on the left, and the finished instruction rebuilds itself on the right, word for word, so you can see exactly how each knob rewrites the request before you ever send it.
Think of it like… a coffee machine with dials for strength, size, and milk. You don't re-plumb the machine each time — you turn a dial and the next cup changes. Here the "cup" is the prompt text, and every dial you turn rewrites it instantly.
The pattern is a tiny one-way data flow: each control writes into a single state object (tone, length, audience, examples), and one pure buildPrompt(state) function maps that state to the assembled prompt string. No control writes to the preview directly — they only update state and call render(), so the preview is always a deterministic function of the inputs. That separation is what lets you add a fifth knob later without touching the other four.
The cause→effect loop is immediate: change one variable, see the one part of the output that moved (it briefly highlights). That tight feedback is the whole teaching value — the reader builds a mental model of how each lever maps to the result, instead of guessing.
Every control feeds the same little builder; the builder hands back one assembled string; the preview shows it. Read it left → right.
Turn any knob on the left. The assembled prompt on the right rebuilds immediately, and the part that changed flashes so you can see which words moved.
Every control updates a shared state object and calls render(). The preview is whatever buildPrompt(state) returns — nothing writes to it directly:
// the only place state becomes text function buildPrompt(state) { const len = LENGTHS[state.length]; // "2–3 sentences" const lines = [ `You are a ` + TONE_VOICE[state.tone] + ` assistant.`, `Write for ` + (state.audience || "a general reader") + `.`, `Keep the reply to about ` + len + `.` ]; if (state.examples) lines.push("Include one concrete example."); return lines.join("\n"); }
Because buildPrompt is pure (same state → same string, no side effects), the preview is always reproducible, and the highlight is just a diff against the previous render. Add a knob by adding one field to state and one line here.
Tone uses real <button aria-pressed> toggles; length is a native <input type="range"> with aria-describedby pointing at its readout; the preview is an aria-live="polite" region so screen readers announce each rebuild without stealing focus.