Follow-up Suggestion Chips
A React component for AI suggested follow-ups: chips stagger in under a reply, and the clicked chip arcs into the input and becomes its text while the rest fold away. Supports wrapping or a single scrolling row, arrow-key navigation and IME-safe keys.
The "you might ask" row that sits under an AI reply. Chips fade in one after another; when you click one, the component creates a copy on the body that flies along a slight upward arc to where the text lands in the input, fading its border and background on the way so only the text arrives. It then writes the text into the input, focuses it and flashes a ring around it, while the other chips fold away. Passing a new set of suggestions replays the entrance automatically. Without a targetRef, only the press feedback and collapse remain and onSelect is called right away.
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 follow-up-suggestion-chips.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<FollowUpSuggestionChips/>
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/follow-up-suggestion-chips.en.jsonOr, once registries are configured in components.json, install it as @webberui/follow-up-suggestion-chips.
Usage
import * as React from "react";
import { FollowUpSuggestionChips } from "@/components/ui/follow-up-suggestion-chips";
export function ChatFooter() {
const inputRef = React.useRef<HTMLInputElement>(null);
const [value, setValue] = React.useState("");
return (
<>
<FollowUpSuggestionChips
suggestions={["週三要帶什麼?", "推薦室內景點", "改查台中"]}
targetRef={inputRef}
onSelect={(text) => setValue(text)}
/>
<input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
suggestions | Array<string | FollowUpSuggestion> | — | Suggested follow-ups; swapping in a new set replays the staggered entrance |
onSelect | (value: string, detail: FollowUpSuggestionSelectDetail) => void | — | Called when a suggestion is picked; with targetRef it fires after the flight ends and the text is written |
targetRef | React.RefObject<HTMLInputElement | HTMLTextAreaElement | null> | — | Input to fly into; when provided, enables the flight animation, then writes the text and focuses it |
mode | "fill" | "send" | "fill" | fill only fills; send lets the parent submit in onSelect (the component only reports it via detail.mode) |
layout | "wrap" | "scroll" | "wrap" | wrap wraps onto new lines; scroll is a single horizontal row with faded edges |
stagger | number | 40 | Stagger between each chip entering (ms) |
duration | number | 220 | Duration of each chip entering and collapsing (ms); also written to --wb-follow-up-suggestion-chips-duration |
flightDuration | number | 420 | Duration of the flight into the input (ms); 0 skips the flight and fills directly |
hideOnSelect | boolean | true | Whether the other chips fold away after a selection |
label | string | "你可能想問" (You might ask) | Small label in front of the row, also used as the group aria-label; pass an empty string to hide it |
disabled | boolean | false | Disable every chip |
className | string | — | Passed through to the outermost container |
FollowUpSuggestion
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id, used as the React key and for selection state |
label | string | — | Text shown on the chip |
value | string | label | Text actually written into the input |
icon | React.ReactNode | — | Small icon on the left of the chip |
FollowUpSuggestionSelectDetail
| Field | Type | Default | Description |
|---|---|---|---|
suggestion | FollowUpSuggestion | — | The selected suggestion (strings are normalised to objects) |
mode | "fill" | "send" | — | The current mode, so the parent can decide whether to submit right away |
index | number | — | Index of the selected suggestion in suggestions |
How it works
- Landing point: the start of the input's text area (left edge + border +
padding-left), vertically centred; when the input already has text, a hidden span with copied font styles measures its width so the chip lands near the end of the text (clamped to the visible area). In atextareait lands on the last line. - Writing the text: the component writes through the native
valuesetter and dispatches aninputevent, so a controlled input'sonChangereceives the new value too; it also callsonSelect, where the parent cansetValueas usual — both paths end in the same state. - The flying copy is portalled into the body with
position: fixed, while the original chip becomes avisibility: hiddenplaceholder, so the row layout never jumps mid-flight. - Timing variables:
--wb-follow-up-suggestion-chips-duration(hover colour transition, equal toduration),--wb-follow-up-suggestion-chips-ease(defaultcubic-bezier(0.23,1,0.32,1)) and--wb-follow-up-suggestion-chips-ring(the colour of the ring flashed on the input on landing). - Passing new
suggestions(a different set of ids) resets the selection and collapse state and replays the staggered entrance.
Accessibility
- The row is a
role="group"labelled bylabel; every chip is a nativetype="button"button - Keyboard: Tab into the row, then ← / → move focus between chips (roving tabindex), Home / End jump to the ends, and Enter / Space select; keydown starts with an IME composition guard
- After a selection focus moves to the
targetRefinput with the caret at the end; anaria-live="polite"region announces "Filled in: …" - When the system "reduce motion" setting is on, there is no flight or stagger: the chips simply fade out and the text is filled in immediately
Subagent Status Card
A React subagent status card nested under its parent message, showing the task, a progress bar and current step, expandable to reveal its streamed output and collapsing to a one-line summary when done; a Group shares one connector line and staggers the cards in.
Test Results Panel
A test results panel whose pass, fail and skip counts roll digit by digit over a smoothly growing ratio bar, with failed tests expanding to show an expected-versus-actual diff.