Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

AI Kit

Chat Message

AI Kit

Chat Message React component for Tailwind CSS v4

Agent Status CardChat Thread
Live props
Content
role
content
model
assistantName
State
status
error
edited
isLatest
defaultFeedback
Appearance
variant
size
bubble
tone
color
showAvatar
caret
codeAccent
Behavior
timestamp
timeFormat
actions
copyFormat
maxUserLines8
longPress
locale
Layout
grouped
Code
import { ChatMessage } from "@/components/ui/chat-message"

<ChatMessage />
  1. Home
  2. Components
  3. AI Kit
  4. Chat Message
Pro

Chat Message React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React AI chat message: the question in a bubble, the answer with its thinking, tool calls, markdown, sources and actions.

Your question sits in a bubble on the right, the answer reads on the left with a small mark. While it streams, three dots wait for the first word, the thinking row shimmers, tools spin inline and a caret follows the text; the actions appear when it finishes. Under the answer: Copy (a check confirms it), thumbs up or down (the pressed one fills and pops, the other steps aside and "Thanks" shows for a moment), Regenerate, the "‹ 2 / 3 ›" version switcher and the "⋯" menu. On the latest answer the actions always show; elsewhere they fade in on hover or focus, and on a phone a tap on the message shows them and its time (one message at a time) while a long press opens an action sheet with the same actions. Edit turns your bubble into a text box: Cmd or Ctrl + Enter sends, Escape cancels and puts focus back on Edit. Images in attachments open a full-screen viewer with swipe, files and sources open in a new tab, long questions fold behind "Show more", and a failed answer shows its error with Retry. Every action is a button in the Tab order with its own name; thumbs are toggles.

Chat Message is a Pro React component in the React AI components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add chat-message, together with Markdown, Reasoning Indicator, Tool Call Timeline, Action Sheet, Avatar and 17 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add chat-message

Chat Message is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import ChatMessage from @/components/ui/chat-message.

import { ChatMessage, type ChatMessageData, type ChatMessageLabels } from "@/components/ui/chat-message"

const labels: Partial<ChatMessageLabels> = {
  assistant: "Summit Assistant",
  assistantWrote: (name) => `${name} replied`,
  regenerate: "Try again",
  good: "Helpful",
  bad: "Not helpful",
  feedbackThanks: "Thanks for the feedback",
}

export function Answer({ message, onRegenerate }: { message: ChatMessageData; onRegenerate: () => Promise<void> }) {
  return (
    <ChatMessage
      {...message}
      isLatest
      labels={labels}
      onRegenerate={onRegenerate}
      onFeedbackChange={(feedback) => console.log(message.id, feedback)}
    />
  )
}

Props

  • roledefault "assistant"
    "user" | "assistant" | "system"

    Who wrote it. user: the person's bubble with Copy and Edit. assistant: the answer with its thinking, tools, sources and response actions. system: a quiet centered line between hairlines, without avatar or actions.

  • contentdefault ""
    string

    The message as markdown (tables, lists, code). Empty on a finished assistant message renders only the footer; empty while pending or streaming shows the typing dots. The playground falls back to the canonical invoice answer.

  • statusdefault "complete"
    "pending" | "streaming" | "complete" | "stopped" | "error"

    pending: sent, nothing back yet (typing dots). streaming: text arriving (caret, aria-busy, actions hidden). stopped: a Stopped note in the meta line, actions still work. error: the error row with Retry.

  • modeldefault ""
    string

    The model that wrote the answer, shown in the meta line (a mono chip in the flat header).

  • errordefault ""
    ReactNode

    The message of the error row when status is "error"; empty shows labels.error.

  • editeddefault false
    boolean

    Adds "edited" to the meta line.

  • variantdefault "default"
    "default" | "bubbles" | "flat"

    default: the person in a bubble on the right, the answer as text on the left (ChatGPT). bubbles: both sides in bubbles (a messenger, a support widget). flat: both on the left with avatars and name headers, no bubbles (team threads, docs).

  • sizedefault "md"
    "sm" | "md"

    Text 14 / 15px, bubble padding and avatars 24 / 28px. sm fits copilot side panels.

  • bubbledefault "soft"
    "soft" | "solid"

    The person's bubble: a light tint of the tone, or a solid fill (black / white when neutral).

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The color of the person's bubble and the edit Send button. Neutral is gray (soft) or black / white (solid).

  • colordefault ""
    string

    Any CSS color for the bubble; replaces the tone. Text on a solid fill turns black or white by lightness.

  • showAvatardefault "assistant"
    "assistant" | "all" | "none"

    Which side shows an avatar. The flat variant always shows both unless "none".

  • assistantAvatar
    ReactNode

    Replaces the assistant's round Sparkles mark: a MatrixOrb, a logo, an image.

  • assistantNamedefault ""
    string

    The assistant's name in the flat header and the screen reader heading; falls back to author.name, then labels.assistant.

  • timestampdefault "hover"
    "hover" | "always" | "never"

    The time (and model) in the meta line: shown on hover, focus or a tap, always, or never. The flat header always shows the time.

  • timeFormatdefault "time"
    "relative" | "time" | "date" | "datetime"

    "16:35", or relative ("5 min. ago", ticking once a minute), a date, or both.

  • actionsdefault "auto"
    "auto" | "always" | "hover" | "never"

    auto: always visible on the latest assistant message, on hover, focus or a tap elsewhere. always / hover force one rule; never hides the action bar (the long-press sheet still works).

  • isLatestdefault false
    boolean

    The last assistant message of a thread (chat-thread sets it). Drives actions="auto".

  • groupeddefault false
    boolean

    A follow-up from the same sender: the avatar and header hide (the text stays aligned) and the bubble tightens its corner on the sender's side.

  • copyFormatdefault "markdown"
    "markdown" | "text"

    What Copy puts on the clipboard: the markdown source, or plain text through markdownToText.

  • caretdefault "dot"
    "dot" | "bar" | "block" | "none"

    The caret at the end of streaming text.

  • codeAccentdefault ""
    string

    The accent of code blocks in the answer and the tool inputs and outputs; empty keeps the code block's own.

  • maxUserLinesdefault 8
    number

    Long questions fold past this many lines behind "Show more"; 0 never folds.

  • longPressdefault true
    boolean

    On touch screens a long press on the message opens an action sheet with every action (and a haptic tick). Links, code and the buttons keep their own touch behavior.

  • localedefault ""
    string

    Times, durations and file sizes. Empty uses UIProvider's locale, then the browser's.

  • createdAt
    Date | string | number

    When it was sent; the meta line, the flat header and the screen reader heading show it.

  • author
    ChatPerson

    { id, name, avatar? }: the person (their avatar and name) or the assistant (its name and picture).

  • attachments
    ChatAttachment[]

    Images in a grid that opens the full-screen viewer (one large, two to four as tiles, more as +N), files as tiles with their type color and size.

  • reasoning
    ChatReasoning

    { content, status?, startedAt?, endedAt?, durationMs? }: the thinking row above the answer (ReasoningIndicator).

  • toolCalls
    ToolCall[]

    The tools it used, as an inline summary that expands into the timeline (ToolCallTimeline variant inline).

  • sources
    ChatSource[]

    Chips under the answer with favicon, domain and number; they open their url in a new tab.

  • id
    string

    The message id, set on the root; keeps the tap-to-show state per message.

  • branch
    { index: number; count: number }

    Which version this is (1-based) of how many: shows the "‹ 2 / 3 ›" switcher when count is above 1, and the text cross-fades on every switch.

  • onBranchChange
    (index: number) => void

    The switcher's previous / next, with the new 1-based index. Without it the arrows are disabled.

  • onCopy
    (text: string) => void

    After copying (Copy is built in).

  • onRegenerate
    () => (void | Promise<unknown>)

    Shows Regenerate on assistant messages; a returned promise keeps it spinning.

  • onEdit
    (content: string) => (void | Promise<unknown>)

    Shows Edit on user messages; Send calls it with the new text (unchanged text just closes the editor). A promise keeps Send loading and the editor open until it resolves.

  • feedback
    "up" | "down" | null

    Controlled thumbs; without onFeedbackChange they are read-only (in the bar and the long-press sheet). The thumbs show when any of feedback, defaultFeedback or onFeedbackChange is set.

  • defaultFeedbackdefault ""
    "up" | "down" | null

    The starting thumbs while uncontrolled; unset (null) shows neither pressed.

  • onFeedbackChange
    (feedback: "up" | "down" | null) => (void | Promise<unknown>)

    Pressing the pressed thumb clears it. Optimistic: a rejected promise rolls the choice back.

  • onRetry
    () => (void | Promise<unknown>)

    Shows Retry in the error row; a promise keeps it loading.

  • menu
    MenuEntry[]

    The "⋯" menu (Share, Read aloud, Report, Delete): a dropdown on desktop, a bottom sheet on phones; its items also join the long-press sheet.

  • onSourceClick
    (source: ChatSource) => void

    Replaces opening the source url.

  • onAttachmentClick
    (attachment: ChatAttachment) => void

    Replaces the default: images open the viewer, files open their url.

  • now
    Date | number

    Pins the clock for relative times and live durations (screenshots, tests). Default: a shared ticker.

  • markdownProps
    Partial<MarkdownProps>

    Passed to the Markdown of the content (labels, linkTarget, components). Keep it stable.

  • reasoningProps
    Partial<ReasoningIndicatorProps>

    Passed to the thinking row (labels, variant, peek). Keep it stable.

  • toolCallProps
    Partial<ToolCallTimelineProps>

    Passed to the inline tool timeline (labels, onApprove, onReject). Keep it stable.

  • labels
    Partial<ChatMessageLabels>

    Every visible and spoken string: you, assistant, youWrote, personWrote(name), assistantWrote(name), system, copy, copied, regenerate, edit, save, cancel, good, bad, feedbackThanks, more, retry, error, stopped, edited, sources, attachments, previous, next, branch(index, count), branchLabel(index, count), showMore, showLess, thinking, attachment(name, size), openImage(name), moreImages(count). English by default (see Parts).

  • classNames
    Partial<Record<"avatar" | "header" | "attachments" | "bubble" | "reasoning" | "tools" | "content" | "typing" | "error" | "sources" | "footer" | "actions" | "action" | "meta" | "editor", string>>

    Extra classes for the inner parts.

Parts

  • ChatMessageActions

    component

    The action bar alone (copy, edit, thumbs, regenerate, versions, menu), for custom message layouts. Props: ChatMessageActionsProps.

  • ChatMessageAttachments

    component

    The image grid with its viewer and the file tiles. Props: ChatMessageAttachmentsProps.

  • ChatMessageSources

    component

    The rail of source chips. Props: ChatMessageSourcesProps.

  • DEFAULT_CHAT_MESSAGE_LABELS

    constant

    Every English string of the message, its action bar, sources, attachments and versions, to spread under a translation.

  • ChatMessageTyping

    component

    The three typing dots of a pending reply, with sr-only text (chat-thread's thinking row uses it). Props: ChatMessageTypingProps.

Examples

Streaming

A reply from the first token: typing dots, the thinking row, two tools, then text with a caret.

Bubbles

A support widget: variant="bubbles", size sm and a solid brand bubble for the customer.

Flat thread

Team thread with avatars, name headers, a system line and an orb as the assistant's avatar.

Error and stopped

An inline error with Retry that succeeds on the second try, and an answer stopped halfway.

Attachments and sources

Four photos in a grid that opens the viewer, a PDF tile, and three source chips.

Editing and versions

Edit a question in place; each version keeps its own answer behind the ‹ › switcher.

Phone

At 390px: tap to show actions, long press for the action sheet, the menu as a bottom sheet.

Dependencies

motionlucide-reactmarkdownreasoning-indicatortool-call-timelineaction-sheetavatarbuttoncollapsiblecopy-buttondropdown-menuimage-lightboxtextareatooltipai-chatfile-typesrelative-timeuse-controllable-stateuse-localeuse-long-pressuse-media-querymotiontonesui-config

Files

  • components/ui/chat-message.tsx

FAQ

Is the React Chat Message component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Chat Message?

Run npx wingo-ui@latest add chat-message. It also adds the Wingo UI items it builds on (Markdown, Reasoning Indicator, Tool Call Timeline, Action Sheet, Avatar and 17 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/chat-message.

How does Chat Message behave on phones?

On the latest answer the actions always show; elsewhere they fade in on hover or focus, and on a phone a tap on the message shows them and its time (one message at a time) while a long press opens an action sheet with the same actions.

See also

Chat ThreadMarkdownReasoning IndicatorTool Call TimelinePrompt InputAI Chatlib

Used in

Chat ThreadChat WidgetAI Chat Page

More React AI components

Conversation ListRule BuilderSuggestion ChipsUsage MeterFluid OrbGrid Reveal
All 17 React AI components

Articles using this component

  • React Markdown Streaming Without Flicker or Scroll Jumps

    Building UI with AI agents · Oct 16, 2026 · 10 min read

  • React AI Chat UI: Streaming, Auto-Scroll and Mobile

    Building UI with AI agents · Oct 9, 2026 · 10 min read

PreviousAgent Status CardNext Chat Thread

You said, 4:32 PM

Which invoices from September are overdue? Put them in a table and suggest what to do next.

4:32 PM

Summit Assistant said, 4:33 PM

I found 3 overdue invoices in September, $22,775.95 in total:

ClientInvoiceAmountLate
Northwind Tech Inc.#2026-0405$12,480.001 day
Greenleaf Pharmacy#2026-0371$7,315.2053 days
Westside Books#2026-0366$2,980.7557 days

What I suggest:

  1. A polite reminder to Northwind Tech Inc., the due date passed yesterday.
  2. A call to Greenleaf Pharmacy: Daniel Cross, (503) 555-0118.
  3. For Westside Books, a formal payment notice. I can use the Final notice template.

Should I draft the emails?

4:33 PM·Summit Pro
last action: —caret=dot · copyFormat=markdown · longPress=on · maxUserLines=8 · codeAccent=—

Hover the answer (or tap it on a phone) for its actions; isLatest keeps them on.