affectUI › Components › Chat

AI Answer

Streamed answer with source cards, citation chips that preview their source, copy, regenerate, feedback and follow-ups.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/ai-answer.json · Import from @/components/ui/ai-answer

When to use

  • Answers from an assistant that searched your docs, the web or connected tools and should show where each claim comes from
  • Research, support or knowledge-base products where people need to check the source before they act

When not to use

  • Casual chat without sources – a plain message bubble is lighter
  • Long documents or code – the answer is made for a few paragraphs and lists

Usage

import { AIAnswer, type AnswerSource } from "@/components/ui/ai-answer";

const sources: AnswerSource[] = [
  { name: "Notion", title: "Offsite plan – shortlist and budget", href: "https://notion.so/…", snippet: "Budget €4,500 for three nights…" },
  { name: "Slack", title: "#team-offsite – who can make which dates", href: "https://slack.com/…" },
];

export function Answer({ text, done }: { text: string; done: boolean }) {
  return (
    <AIAnswer
      question="Where should we hold the team offsite?"
      sources={sources}
      // Blank lines: paragraphs · "- ": list · **bold** · [1] or [1,2]: citations (1-based)
      answer={text}
      streaming={!done}
      onRegenerate={() => regenerate()}
      onFeedback={(value, reason) => track("answer_feedback", { value, reason })}
    />
  );
}

Props

NameTypeDefaultDescription
questionstringShown as the title above the sources
sourcesAnswerSource[]Each { title, name, href?, icon?, snippet? }; name is the short label on the citation chip
answerstringBlank lines make paragraphs, lines starting with "- " a list, **bold**, [1] or [1,3] cite sources (1-based). It may grow while streaming; a different text starts over
streamingbooleanfalseMore text is still coming from your model – keeps the cursor going once it has caught up
animatebooleantrueStream the text in; false shows it at once
speednumber38Words per second while streaming in
followUpsstring[]Related questions shown below the finished answer
onFollowUp(question: string) => voidA follow-up was clicked
onRegenerate() => voidRegenerate was clicked – the answer streams in again; pass a new answer to replace it
onStop() => voidStop was clicked while writing – cancel your request here
onFeedback(value: "up" | "down", reason?: string) => voidThumbs up, or thumbs down with the reason that was picked
feedbackReasonsstring[]["Not accurate", "Wrong source", "Too long", "Out of date"]Reasons offered after a thumbs down
onCopy(text: string) => voidCalled with the copied text: the answer without markup plus a numbered source list
classNamestringExtra class on the root

Keyboard

  • Tab: Moves through source cards, citation chips, follow-ups and actions
  • Esc: Closes the source preview of the focused chip

Accessibility

  • Citation chips are buttons named after their sources; the preview is a tooltip linked to the chip
  • The answer is marked busy while it streams, so screen readers read it once it's complete
  • Copy, feedback and status changes are announced in a polite live region

Responsive

  • Fills its container up to 640px; source cards scroll sideways on narrow screens
  • Tap a citation chip to open its preview on touch screens
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Thinking State – AI working panel with a live timer, searched sources and thinking steps that appear one by one
  • Chat Composer – Prompt composer with model picker, voice input, app connections and approval request
  • Comment Thread – One comment with replies – an assistant reports what it did, folds open details and answers @mentions

Guides