AI chat

Chat message

One turn in a conversation.

Server safeSource

Import

import { ChatMessage } from '@the_viveksingh/vivek-ui'

A conversation turn

role drives the alignment and the accessible role, so user, assistant and system turns are distinguishable without reading the colours.

You are chatting with the support assistant.
How do I override a component's padding?
Pass a className. Library selectors sit inside :where(), so they carry no specificity and your class wins outright.
<ChatMessage role="system" content="You are chatting with the support assistant." />

<ChatMessage
  role="user"
  name="You"
  avatar={<Avatar name="Vivek Kumar Singh" size="sm" />}
  content="How do I override a component's padding?"
  timestamp={sentAt}
  status="sent"
/>

<ChatMessage
  role="assistant"
  name="Assistant"
  avatar={<Avatar fallback="AI" size="sm" />}
  content="Pass a className. Library selectors sit inside :where()."
  timestamp={repliedAt}
/>

Flat variant

No bubble. Better for long answers, where a bubble becomes a wall of colour.

Summarise this thread.
Three open questions remain: pricing, the migration window, and who signs off.
<ChatMessage variant="flat" role="user" name="You" content="Summarise this thread." />
<ChatMessage variant="flat" role="assistant" name="Assistant" content="Three open questions remain." />

Delivery status

sending, sent and error each get a visually hidden label, so a failed send is announced rather than only tinted. Use actions for the retry affordance.

Sending this one now
This one landed
This one failed to send
<ChatMessage role="user" content="Sending this one now" status="sending" />
<ChatMessage role="user" content="This one landed" status="sent" />
<ChatMessage
  role="user"
  content="This one failed to send"
  status="error"
  actions={<Button size="sm" variant="ghost" onClick={retry}>Retry</Button>}
/>

Props

Generated from the package's own type declarations, so this table cannot drift from the code.

Props for ChatMessage
PropTypeDefaultDescription
roleChatMessageRoleWho is speaking. Defaults to `assistant`.
contentReactNodeThe turn's body. A `ReactNode`, never a string that gets parsed — React escapes whatever goes in here, which is the whole reason this family needs no sanitiser. Falls back to `children` when omitted.
avatarReactNodeLeading slot, typically an `<Avatar />`. Purely decorative for a11y purposes.
namestringDisplay name of the speaker. Defaults to a label derived from `role`.
timestampDate | string | numberWhen the turn happened. A `Date` or epoch number is formatted for the current locale; a **string is rendered verbatim**, so `timestamp="2 min ago"` works and SSR output stays byte-identical when you pre-format.
statusChatMessageStatusDelivery state. Defaults to `sent`, which renders no status text.
actionsReactNodeTrailing controls (copy, retry, thumbs up…). Rendered after the content.
variantChatMessageVariant`bubble` for a chat balloon, `flat` for a document-style transcript.
formatTimestamp(date: Date) => stringOverride the default `Date` formatting. Also the escape hatch for strict SSR.
statusLabelsPartial<Record<ChatMessageStatus, string>>Localise the status text (also used in the accessible name).

Every remaining prop is spread onto the root element, so all standard HTML and ARIA attributes work. className and style are merged with the library's own, never replaced, and the ref forwards to the root DOM node.

Rendering

Server safe

ChatMessage carries no 'use client' directive and renders directly in a React Server Component. No client JavaScript is shipped for it.