Messaging

A flat chat surface with a composer, replies, reactions, and attachments.

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.json
Usage
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

PropTypeDefaultDescription
currentUserIdstringβ€”Viewer id β€” messages with this `senderId` render as own (right-aligned) bubbles.
messagesChatMessage[]β€”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.
placeholderstring"Type a message…"Composer placeholder.
reactionChoicesstring[]["πŸ‘","❀️","πŸ˜‚","πŸŽ‰","πŸ‘€","πŸ™"]Emoji shown in the reaction picker popover.
emptyStateReactNodeβ€”Rendered when `messages` is empty. Defaults to a built-in Empty card ("No comments yet").
allowAttachmentsbooleantrueEnable the paperclip + drag-and-drop attachments.
allowReactionsbooleantrueEnable emoji reactions and the reaction picker.
allowRepliesbooleantrueEnable replying (quoted preview in the composer).
allowMessageActionsbooleantrueShow 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.
showTimestampsbooleantrueShow per-message timestamps.
showAvatarsbooleantrueShow sender avatars.
showComposerbooleantrueRender the composer (set false for a read-only transcript).
showTypingIndicatorbooleantrueShow the typing indicator row.
autoScrollbooleantruePin to newest + show the unread jump-to-latest FAB when scrolled up.
scrollMaskbooleanfalseSoft-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.
classNamestringβ€”Class override on the chat shell.

ChatMessage

PropTypeDefaultDescription
idstringβ€”Stable key.
senderIdstringβ€”Matched against `currentUserId` for alignment.
senderNamestringβ€”Display name + avatar fallback.
senderAvatarstring | nullβ€”Avatar image URL (falls back to initials).
textstringβ€”Message body (optional for attachment-only messages).
timestampDate | 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).
attachmentsChatAttachment[]β€”`{ id, name, url?, size?, type? }` β€” images render as inline thumbnails.
reactionsChatReaction[]β€”`{ emoji, users: string[] }` β€” `users.length` is the chip count; includes `currentUserId` when the viewer reacted.
replyToChatReplyRefβ€”`{ id, senderName, text }` β€” quoted parent rendered above the bubble.