Someone visiting a generator usually has something to make. They want to start quickly, see what each change does, and leave with code that behaves the same in their project as it did in the preview. This guide covers the four parts of a generator page, the preview, the controls, the output, and the text around them, using decisions and code from GradientDeck's own tools as examples.
Show a working result immediately
The first thing on the page should be the tool, already showing a result. Give the preview enough space to judge the effect: a mesh gradient needs a large area, a box shadow needs a card on a plain surface, a text gradient needs a large heading. Put the controls beside the preview on wide screens and directly below it on a phone, so a change and its effect are visible together.
Pick the starting state with care. It is the example most people judge the tool by, and many never change it. It should be typical rather than dramatic, and it should be deterministic if the page is server-rendered. GradientDeck's mesh tool fixes its starting state for this reason:
// Fixed starting mesh: a random one on first render differs between server and
// client and causes a hydration mismatch. Shuffle still randomizes on demand.
const initialColorStops: ColorStop[] = [
{ color: "#47e4e0", positionX: 18, positionY: 20 },
{ color: "#5f81e4", positionX: 80, positionY: 16 },
{ color: "#f67ff5", positionX: 74, positionY: 82 },
{ color: "#f6f6d9", positionX: 22, positionY: 78 },
];Randomness belongs behind a button (Shuffle) or a shortcut, after the page has loaded. Avoid a second, decorative demonstration elsewhere on the page that competes with the working tool for attention.
Name the controls plainly
Use labels that describe what changes: Direction, Blur, Duration, Tint opacity. Show the current value next to each slider. A short label and a visible number are more useful than a sentence of encouragement, and the number lets someone reproduce a result or compare two settings.
<label for="blur">Blur</label>
<input id="blur" type="range" min="0" max="40" value="16"
aria-describedby="blur-value">
<output id="blur-value" for="blur">16px</output>A native range input with a <label> is keyboard-operable and announced correctly without extra work. If you build a custom slider, you take on arrow-key handling and the slider ARIA role yourself.
Add a note only when a control has a consequence that is not obvious, such as a setting that the exported code cannot reproduce, or one that affects browser support.
Keyboard shortcuts that do not get in the way
A single-key shortcut, such as G for generate, is quick for repeat users. It must not fire while someone is typing a hex value or working inside a color picker popover. GradientDeck's tools use one guard for this:
function isShortcutBlocked(event: KeyboardEvent) {
if (event.defaultPrevented || event.repeat || event.metaKey || event.ctrlKey || event.altKey) return true;
const target = event.target;
return (
target instanceof HTMLElement &&
(target.isContentEditable ||
Boolean(target.closest('input, textarea, select, [role="dialog"], [data-tool-search]')))
);
}Ignoring modifier keys keeps Ctrl+G and Cmd+G working as the browser intends. Ignoring repeats stops a held key from cycling through dozens of results. Show the shortcut next to the button it triggers, so people can find it.
Derive the preview and the code from the same state
The output panel should always show exactly what the preview shows. The reliable way to guarantee that is to build both from one piece of state, rather than updating them separately:
const radialLayer = (stop: ColorStop) =>
`radial-gradient(at ${stop.positionX}% ${stop.positionY}%, ${stop.color} 0px, transparent 50%)`;
const cssCode = useMemo(
() => `background:\n ${colorStops.map(radialLayer).join(",\n ")},\n ${backgroundColor};`,
[colorStops, backgroundColor]
);
const backgroundStyle = useMemo(
() => ({ background: `${colorStops.map(radialLayer).join(", ")}, ${backgroundColor}` }),
[colorStops, backgroundColor]
);The preview's inline style and the copied CSS come from the same radialLayer function, so they cannot drift apart. Put the format selector (CSS, Tailwind, and so on) directly above the code it controls, and the copy button inside the same panel.
When the export cannot use the same code
Some exports have to re-implement the preview. GradientDeck's mesh PNG download redraws the CSS mesh on a canvas. A canvas gradient that fades to transparent fades toward transparent black, which darkens the edges, while CSS gradients do not show that dark fringe. The fix is to fade each color to a fully transparent version of itself:
gradient.addColorStop(0, stop.color);
// Fade the same color to zero alpha so the edge does not darken.
gradient.addColorStop(0.5, colord(stop.color).alpha(0).toRgbString());Whenever an export is produced by different code from the preview, compare them side by side at a few settings before shipping.
Make copying dependable
navigator.clipboard.writeText() returns a promise that can reject, for example on a page not served over HTTPS or when the browser denies permission. Show the confirmation only after it succeeds, keep it brief, and announce it to screen readers with a status region:
const copyCss = async () => {
try {
await navigator.clipboard.writeText(cssCode);
} catch {
setCopyError(true); // show "Copy failed — select the code manually"
return;
}
setCopied(true);
window.setTimeout(() => setCopied(false), 1800);
};
// …
<p role="status" aria-live="polite">{copied ? "Copied" : ""}</p>The status element should already exist in the page before the message appears; screen readers watch existing live regions for changes. Do not cover the code or the preview with the confirmation, and keep the code selectable as a fallback.
Explain the exceptions
The text on a generator page earns its place by answering questions the tool raises. Generic paragraphs about why gradients are popular do not help anyone. Notes like these do:
- Differences between the preview and the export. GradientDeck's liquid glass page says its Tailwind export leaves out chromatic edge and noise, which the CSS export includes.
- Format details. The Tailwind colors page explains that v4 defines colors in OKLCH, so the HEX, RGB, and HSL values are the nearest sRGB equivalents and a few saturated shades look slightly duller in those formats.
- Privacy. The image color picker says colors are extracted in the browser and uploaded images are not sent to a server.
- How to use the output. The mesh page explains that the CSS is one background declaration and that removing a layer removes that color.
- Useful errors when an action fails, such as an unsupported file type on upload.
Keep these notes short and close to the tool. Longer explanations, worked examples, and background belong in a linked guide, where they do not push the tool down the page.
This is also what Google's guidance on helpful content asks about: whether a page provides original information or analysis, and whether it provides substantial value compared with other pages in search results. A generator page with the same tool and the same boilerplate as dozens of others does not; one that explains its own output and limitations has something only it can say.
Check it on a phone
- At 320 CSS pixels wide, the width WCAG's Reflow criterion uses, the preview, controls, and copy button should all be reachable without horizontal scrolling. Long code lines can scroll inside their own panel.
- Open the on-screen keyboard while editing a hex value and confirm the field and the preview are still visible.
- Drag handles and color points need a target large enough for a finger, and a way to adjust them without dragging, such as a numeric input.
- Long-press and scroll gestures should not be captured by the preview unless the preview needs them.
To see these choices together, try the color palette generator (G for a new palette, copy formats, and export to CSS variables, PNG, or SVG) or the mesh gradient generator.
References
Google Search Central: Creating helpful, reliable, people-first content