PN
Morning! The lunar theme mockups are in the shared folder π
10:13 PM
YO
Love it. The indigo bubbles read really well on dark.
10:15 PM
PN
Here's the spec sheet for the composer states.
10:27 PM
YO
Priya NadarHere's the spec sheet for the composer states.
Replying to confirm β drag-and-drop + reactions are wired up.
10:40 PM
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/chat.jsonUsage
Pass a flat `messages` array β each entry carries its own attachments, reactions, and replyTo β plus `onSend` / `onReact` handlers.
TSImport
import { Chat } from "@/components/ui/chat";TSExample
import { Chat, type ChatMessage } from "@/components/ui/chat";
const messages: ChatMessage[] = [
{ id: "1", senderId: "them", senderName: "Priya", text: "Hey! π" },
{
id: "2",
senderId: "me",
senderName: "You",
text: "Hi β got the files?",
status: "read",
replyTo: { id: "1", senderName: "Priya", text: "Hey! π" },
reactions: [{ emoji: "π", users: ["them"] }],
},
];
export function Example() {
return (
<Chat
currentUserId="me"
messages={messages}
onSend={({ text, attachments, replyToId }) => console.log(text, attachments, replyToId)}
onReact={(messageId, emoji) => console.log(messageId, emoji)}
/>
);
}Composition
Anatomy of the Chat component.
Chat (single flat component)
βββ currentUserId="β¦" (own-vs-other bubble alignment)
βββ messages={ChatMessage[]} (the single source of truth)
β βββ { id, senderId, senderName, senderAvatar?, text?, timestamp?, status? }
β βββ attachments?: [{ id, name, url?, size?, type? }] (images preview inline)
β βββ reactions?: [{ emoji, users: string[] }] (count = users.length)
β βββ replyTo?: { id, senderName, text } (quoted parent message)
βββ onSend={({ text, attachments, replyToId }) => β¦}
βββ onReact={(messageId, emoji) => β¦}
βββ onTyping={(typing) => β¦} (debounced from the composer)
βββ typingUsers={[{ id, name }]} (renders the animated indicator)
βββ reactionChoices={["π","β€οΈ",β¦]} (emoji palette in the reaction picker)
βββ theme="default" | "lunar"
βββ variant="card" | "flat" (flat = no outline/bg/outer padding)
βββ onEditMessage / onDeleteMessage (own-message actions menu β UI only)
βββ emptyState={<ReactNode>} (default: built-in "No comments yet" Empty card)
βββ feature toggles (all default true):
allowAttachments Β· allowReactions Β· allowReplies Β· allowMessageActions
showTimestamps Β· showAvatars
showComposer Β· showTypingIndicator Β· autoScroll Β· scrollMask
Built-in behavior: drag-and-drop files onto the surface to attach, hover a
message for Reply / React actions (own messages also get an Edit / Delete
dropdown), the composer textarea auto-resizes, and a "jump to latest" FAB
with an unread count appears when scrolled up.
The hooks β useAutoScroll, useAutoResize, useTypingIndicator β are exported
from the same file for fully custom layouts.Interactive
A live `<Chat>` on the default Card surface. Send a message, drag files onto the surface to attach, hover a bubble to reply or react β your own messages also get an Edit/Delete dropdown (UI-only; wire `onEditMessage`/`onDeleteMessage` to your mutations).
PN
Morning! The lunar theme mockups are in the shared folder π
10:13 PM
YO
Love it. The indigo bubbles read really well on dark.
10:15 PM
PN
Here's the spec sheet for the composer states.
10:27 PM
YO
Priya NadarHere's the spec sheet for the composer states.
Replying to confirm β drag-and-drop + reactions are wired up.
10:40 PM
Empty state
With an empty `messages` array, `<Chat>` renders its built-in Empty card ("No comments yet"). Pass `emptyState` to swap in custom content. Send a message to watch the transcript take over.
No comments yet
Start the conversation β your message shows up here.
Flat
`variant="flat"` drops the outline, background, rounding, and outer padding so the chat sits flush inside your own container β every internal behavior (replies, reactions, actions, composer) is unchanged.
PN
Morning! The lunar theme mockups are in the shared folder π
10:13 PM
YO
Love it. The indigo bubbles read really well on dark.
10:15 PM
PN
Here's the spec sheet for the composer states.
10:27 PM
YO
Priya NadarHere's the spec sheet for the composer states.
Replying to confirm β drag-and-drop + reactions are wired up.
10:40 PM
API Reference
Props for the Chat component and each message.
Chat
| Prop | Type | Default | Description |
|---|---|---|---|
currentUserId | string | β | Viewer id β messages with this `senderId` render as own (right-aligned) bubbles. |
messages | ChatMessage[] | β | The flat message list. Each entry carries its own `attachments`, `reactions`, and `replyTo`. |
onSend | (payload: ChatSendPayload) => void | β | Fires on submit with `{ text, attachments: File[], replyToId? }`. |
onReact | (messageId, emoji) => void | β | Fires when a reaction chip or picker emoji is clicked. |
onTyping | (typing: boolean) => void | β | Debounced typing signal from the composer. |
typingUsers | { id, name }[] | [] | Users currently typing β renders the animated dots. |
placeholder | string | "Type a messageβ¦" | Composer placeholder. |
reactionChoices | string[] | ["π","β€οΈ","π","π","π","π"] | Emoji shown in the reaction picker popover. |
emptyState | ReactNode | β | Rendered when `messages` is empty. Defaults to a built-in Empty card ("No comments yet"). |
allowAttachments | boolean | true | Enable the paperclip + drag-and-drop attachments. |
allowReactions | boolean | true | Enable emoji reactions and the reaction picker. |
allowReplies | boolean | true | Enable replying (quoted preview in the composer). |
allowMessageActions | boolean | true | Show an edit/delete dropdown on the viewer's own messages (hover the bubble). UI-only β wire mutations via the callbacks below. |
onEditMessage | (message: ChatMessage, text: string) => void | β | Fires when an inline edit is saved with the new text. The edit form (textarea + Save/Cancel, Enter saves, Esc cancels) is built in β update `messages` here. |
onDeleteMessage | (message: ChatMessage) => void | β | Fires when "Delete message" is chosen from a message's actions menu. |
showTimestamps | boolean | true | Show per-message timestamps. |
showAvatars | boolean | true | Show sender avatars. |
showComposer | boolean | true | Render the composer (set false for a read-only transcript). |
showTypingIndicator | boolean | true | Show the typing indicator row. |
autoScroll | boolean | true | Pin to newest + show the unread jump-to-latest FAB when scrolled up. |
scrollMask | boolean | false | Soft-fade the top + bottom edges of the scroll area so content dissolves under them instead of hard-clipping. |
theme | "default" | "lunar" | "default" | `lunar` is a dark, indigo-tinted theme; `default` uses surface tokens. |
variant | "card" | "flat" | "card" | `card` wraps the chat in a rounded, ring-outlined surface with outer padding; `flat` drops the outline, background, rounding, and outer padding so it sits flush in your own container. |
className | string | β | Class override on the chat shell. |
ChatMessage
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | β | Stable key. |
senderId | string | β | Matched against `currentUserId` for alignment. |
senderName | string | β | Display name + avatar fallback. |
senderAvatar | string | null | β | Avatar image URL (falls back to initials). |
text | string | β | Message body (optional for attachment-only messages). |
timestamp | Date | string | number | β | Rendered as a localized time. |
status | "sending" | "sent" | "delivered" | "read" | β | Optional delivery state you can track on a message (not rendered by default). |
attachments | ChatAttachment[] | β | `{ id, name, url?, size?, type? }` β images render as inline thumbnails. |
reactions | ChatReaction[] | β | `{ emoji, users: string[] }` β `users.length` is the chip count; includes `currentUserId` when the viewer reacted. |
replyTo | ChatReplyRef | β | `{ id, senderName, text }` β quoted parent rendered above the bubble. |