{"slug":"follow-up-list","category":"text","job":"start","added":"2026-09-12T12:42Z","title":"Follow-Up List","description":"A vertical list of suggested next questions shown after a response, each sendable with a tap.","url":"https://ai-patterns.app/patterns/text/follow-up-list","markdown":"https://ai-patterns.app/patterns/text/follow-up-list.md","json":"https://ai-patterns.app/api/patterns/text/follow-up-list","seenIn":[],"spec":"# Follow-Up List\n\n## Summary\nA vertical list of suggested next questions shown after an assistant response, each one a full-width row a user can tap to send as their next message. It turns \"what else could I ask?\" into a menu instead of a blank composer.\n\n## When to use\n- After a response where the model can anticipate a handful of natural next questions (a docs answer, a research summary, a completed task) and surfacing them saves the user from typing.\n- When the suggestions themselves carry information worth reading in full — multi-clause or slightly long questions that would wrap awkwardly as chips.\n- As the primary next action after a response, when there isn't already a busy row of inline actions (copy, thumbs, share) competing for the same space.\n\n## When not to use\n- When suggestions are short, single-topic phrases meant to sit alongside other inline actions (copy, regenerate) — use compact wrapping chips instead, so the row stays low-footprint.\n- Immediately after every single message in a fast back-and-forth chat — it adds visual weight the user will mostly skip past; reserve it for points where the conversation could naturally branch.\n- When there's only one sensible follow-up — a single suggestion reads better as an inline link or a one-line prompt, not a list.\n\n## Anatomy\n- A vertical stack of rows, each with a leading \"corner-down-right\" arrow icon (echoing \"this leads to a reply\") and the suggestion text.\n- No dividers between rows — spacing and the hover highlight separate them.\n- No container border or background — the list sits directly under the response it follows, reading as part of that turn rather than a separate card.\n\n## Behavior\n- Rows animate in with a slight upward fade, staggered a beat apart, so the list doesn't slam onto the screen the instant the response finishes.\n- Each row is a full-width button: on hover or keyboard focus it shows a pointer cursor, a subtle background highlight, and its text and arrow brighten to full foreground, so it reads as clickable before it is clicked.\n- Clicking a row sends that suggestion as the user's next message immediately — it is not a two-step \"fill the composer, then let the user edit\" interaction.\n- Rows appear only once the response they follow has finished streaming, never while it's still generating.\n\n## Content guidelines\n- Write suggestions as full first-person questions the user could ask verbatim (\"How do I migrate historical data if I switch from Google Analytics\"), not fragments or topic labels.\n- Keep the list short — three to five suggestions. More than that stops being a quick scan and starts being a list to read.\n- Order by how likely the user is to want it next, not alphabetically or by topic grouping.\n\n## Accessibility\n- Each row is a real `<button>` inside a `<ul>`/`<li>`, reachable and activatable by keyboard alone.\n- The leading icon is decorative (`aria-hidden`) — the row's accessible name comes from the suggestion text alone.\n- Rows are large tap targets (full-width, generous vertical padding) so the pattern works as well on touch as with a pointer.\n\n## Related patterns\n- **Streaming Text** — Use its follow-up chips instead when short suggestions should wrap inline with other post-response actions.\n- **Refinement Controls** — Use it instead when the suggestion is a change to the current answer rather than a next question.\n","code":"\"use client\";\n\nimport * as React from \"react\";\nimport { motion } from \"motion/react\";\nimport { CornerDownRight } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface FollowUpListProps {\n  suggestions: string[];\n  onSelect?: (suggestion: string) => void;\n  className?: string;\n}\n\nexport function FollowUpList({ suggestions, onSelect, className }: FollowUpListProps) {\n  if (suggestions.length === 0) return null;\n\n  return (\n    <ul className={cn(\"flex w-full max-w-md flex-col\", className)}>\n      {suggestions.map((suggestion, i) => (\n        <motion.li\n          key={suggestion}\n          initial={{ opacity: 0, y: 4 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.2, delay: i * 0.05 }}\n        >\n          <button\n            type=\"button\"\n            onClick={() => onSelect?.(suggestion)}\n            className=\"group -mx-2 flex w-[calc(100%+1rem)] cursor-pointer items-center gap-2.5 rounded-md px-2 py-3 text-left transition-colors hover:bg-accent focus-visible:bg-accent focus-visible:outline-none\"\n          >\n            <CornerDownRight\n              className=\"size-3.5 shrink-0 text-muted-foreground transition-colors group-hover:text-foreground group-focus-visible:text-foreground\"\n              aria-hidden\n            />\n            <span className=\"flex-1 text-sm text-foreground/90 transition-colors group-hover:text-foreground group-focus-visible:text-foreground\">\n              {suggestion}\n            </span>\n          </button>\n        </motion.li>\n      ))}\n    </ul>\n  );\n}\n","dependencies":["lucide-react","motion"],"localImports":["@/lib/utils"]}