Sakani

Chat

The three pieces of a messaging UI: the thread, the conversation list, and the composer.

AK
Amara Chen09:14
Can you take a look at the Q3 forecast before standup?
Looking now — the marketplace line seems off.
09:16
Amara added Daniel to this conversation

No formatting, no state

Timestamps are ReactNode, not Date: these components never format a time, so "09:14" vs "2 minutes ago" stays your decision, along with the locale and the relative-time refresh. Likewise the composer won't clear itself after onSend — you own the draft.

Attachments and files

forecast-q3.xlsx248 KB

Conversation list

AKAmara Chen09:14Can you take a look at the Q3 forecast?3
DODaniel Oseityping...
PRPriya RamanYesterdayThanks!

Composer

MessageBubble props

PropTypeDefaultDescription
type'received' | 'sent' | 'system''received'Sent aligns right; system is the centred, unattributed notice.
content'text' | 'image' | 'file''text'What the body holds. Image and file change the layout inside the bubble.
childrenReactNodeText body, for content="text".
imageReactNodeThe image node, for content="image".
fileName / fileSizestringFile meta, for content="file".
avatarReactNodeAuthor avatar. Received messages only.
authorNameReactNodeAuthor name. Received messages only.
timestampReactNodePre-formatted time — the component does no date formatting.
reactionsReactNodeReaction row. Received messages only.
readbooleanfalseRead receipt. Sent messages only.

ConversationItem props

PropTypeDefaultDescription
state'default' | 'hover' | 'active' | 'unread' | 'typing' | 'muted''default'Typing replaces the preview with an indicator; unread shows the count.
avatarReactNodeRequired. The conversation's avatar.
nameReactNodeConversation or person name.
previewReactNodeLast-message preview. Ignored while typing.
timestampReactNodePre-formatted time of the last message.
unreadCountnumberBadge count, shown when state is unread.
onClick() => voidOpens the conversation.

ChatComposer props

PropTypeDefaultDescription
state'default' | 'typing' | 'uploading' | 'disabled''default'Uploading reveals the progress row.
valuestringControlled draft text.
onChange(value: string) => voidFires with the text itself, not an event.
onSend() => voidFires on submit. Clearing the draft afterwards is your job.
onAttachmentsChange(files: { name; type; dataUrl }[]) => voidFires with attachments already read as data URLs.
uploadName / uploadProgressstring / numberUpload meta shown while state is uploading.
onCancelUpload() => voidCancels the in-flight upload.