Generate-and-Pick Flow
Prompt, skeletons, then a row of generated results slides in for the user to pick one that expands for confirmation — the standard flow for image and video generators.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
The most common flow in image and video generators: type → generate → a row of options slides past → pick one. The component models it as a four-phase state machine (idle, generating, results, picked): the input row has an auto-growing multi-line prompt plus ratio and count chips; while generating, skeleton cards sized to the ratio show a diagonal shimmer; when results arrive they replace the skeletons one by one, sliding in from the right, and the row scrolls horizontally; click any card and it expands into the preview above with a shared layout animation, with "Use this one", "Regenerate" and "Vary" below. Generation itself is entirely up to your onGenerate.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/generate-and-pick-flow.en.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<GenerateAndPickFlow/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add "https://webberui.com/r/generate-and-pick-flow.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/generate-and-pick-flow.
Usage
import {
GenerateAndPickFlow,
type GenerateAndPickFlowResult,
} from "@/components/ui/generate-and-pick-flow";
async function generate(
prompt: string,
options: { ratio: string; count: number },
): Promise<GenerateAndPickFlowResult[]> {
const res = await fetch("/api/generate", {
method: "POST",
body: JSON.stringify({ prompt, ...options }),
});
const images: { id: string; url: string }[] = await res.json();
return images.map((img, i) => ({
id: img.id,
title: `版本 ${i + 1}`,
preview: <img src={img.url} alt="" className="object-cover" />,
}));
}
<GenerateAndPickFlow
defaultPrompt="黃昏海邊的紙飛機,水彩風格"
onGenerate={generate}
onPick={(result) => console.log("採用", result.id)}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
onGenerate | (prompt: string, options: GenerateAndPickFlowOptions) => Promise<GenerateAndPickFlowResult[]> | — | Generator; on reject, error cards and a retry button are shown |
onPick | (result: GenerateAndPickFlowResult) => void | — | Called when "Use this one" is pressed in the enlarged preview |
defaultPrompt | string | "" | Initial prompt text |
placeholder | string | "描述你想生成的畫面…" (Describe the image you want…) | Prompt placeholder |
ratios | string[] | ["1:1", "16:9", "9:16"] | Ratio chips; the first is the default |
count | number | — | Controlled result count (2–6) |
defaultCount | number | 4 | Initial count in uncontrolled mode |
onCountChange | (count: number) => void | — | Called when the count changes |
status | "idle" | "generating" | "results" | "picked" | — | Controlled phase; managed internally when omitted |
onStatusChange | (status: GenerateAndPickFlowStatus) => void | — | Called when the phase changes |
shimmer | boolean | true | Whether skeleton cards show a diagonal shimmer |
accent | string | — | Accent for the selected outline; falls back to --wb-generate-pick-accent |
stagger | number | — | Delay between results sliding in, in ms; reads --wb-generate-pick-stagger when omitted |
className | string | — | Passed through to the outermost container |
GenerateAndPickFlowResult
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id, used for the layout animation and focus restoration |
preview | ReactNode | — | Preview content; fills the ratio-cropped card |
title | string | — | Title, shown under the enlarged preview and used as the card's accessible name |
GenerateAndPickFlowOptions
| Field | Type | Default | Description |
|---|---|---|---|
ratio | string | — | The selected ratio |
count | number | — | How many results to generate |
variationOf | GenerateAndPickFlowResult | — | The result being varied when "Vary" is pressed; undefined for normal generation |
How it works
- CSS variables:
--wb-generate-pick-shimmer(default1.4s) sets the length of one shimmer pass,--wb-generate-pick-stagger(default80ms) the interval between results appearing, and--wb-generate-pick-accent(default#8b5cf6) the selected outline color. Theaccentandstaggerprops override their variables when set - One-by-one reveal: once the promise resolves, skeletons are swapped for results at the stagger interval, each sliding in from x 60px and opacity 0 on a spring (stiffness 300, damping 30)
- Enlarged preview: the clicked card flies up via a shared layoutId (stiffness 260, damping 28), leaving a same-size "Selected" outline in place while the other cards shrink and dim; Esc goes back
- Horizontal scrolling: the results row uses scroll-snap and maps the mouse wheel to horizontal scrolling, handing the wheel back to the page at either end
- No mix-ups: when Generate is pressed repeatedly, only the latest call's results are used; earlier promises that resolve later are discarded
- Vary: "Vary" calls
onGenerateagain with the same prompt and passes the varied result inoptions.variationOf
Accessibility
- Enter submits the prompt and Shift+Enter inserts a newline; Enter pressed while an IME (e.g. Chinese) is composing does not submit
- The results row is
role="listbox"and each card isrole="option"witharia-selected; Left/Right arrows and Home/End move focus, Enter or Space picks - While generating the row has
aria-busy, and anaria-liveregion announces "Generating N images" and "Generated N images"; failures show the error and a retry button withrole="alert" - After picking, focus moves to "Use this one"; Esc returns to the results row and puts focus back on the original card
- When the user's system has reduce motion enabled, skeletons have no shimmer, results appear immediately, and picking switches directly with no layout flight
Synced Typing Grid
A synced typing showcase React component that types the same prompt simultaneously into N differently styled input shells — chat, terminal, search bar and document editor — for side-by-side interface comparisons.
Agent Relay Workspaces
Agent workspaces take turns flipping in with 3D rotation, each running a mock task and handing its output to the next, ending in a combined result.