AI Interface Components

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

40
420
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
suggestionsArray<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
targetRefReact.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
staggernumber40Stagger between each chip entering (ms)
durationnumber220Duration of each chip entering and collapsing (ms); also written to --wb-follow-up-suggestion-chips-duration
flightDurationnumber420Duration of the flight into the input (ms); 0 skips the flight and fills directly
hideOnSelectbooleantrueWhether the other chips fold away after a selection
labelstring"你可能想問" (You might ask)Small label in front of the row, also used as the group aria-label; pass an empty string to hide it
disabledbooleanfalseDisable every chip
classNamestring—Passed through to the outermost container

FollowUpSuggestion

FieldTypeDefaultDescription
idstring—Unique id, used as the React key and for selection state
labelstring—Text shown on the chip
valuestringlabelText actually written into the input
iconReact.ReactNode—Small icon on the left of the chip

FollowUpSuggestionSelectDetail

FieldTypeDefaultDescription
suggestionFollowUpSuggestion—The selected suggestion (strings are normalised to objects)
mode"fill" | "send"—The current mode, so the parent can decide whether to submit right away
indexnumber—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 a textarea it lands on the last line.
  • Writing the text: the component writes through the native value setter and dispatches an input event, so a controlled input's onChange receives the new value too; it also calls onSelect, where the parent can setValue as usual — both paths end in the same state.
  • The flying copy is portalled into the body with position: fixed, while the original chip becomes a visibility: hidden placeholder, so the row layout never jumps mid-flight.
  • Timing variables: --wb-follow-up-suggestion-chips-duration (hover colour transition, equal to duration), --wb-follow-up-suggestion-chips-ease (default cubic-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 by label; every chip is a native type="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 targetRef input with the caret at the end; an aria-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