Skip to content

FRD: Chat Thread Widget

Document Summary

FieldDetails
Feature NameChat Thread Widget
StatusDraft
OwnerDavid Holmes
ContributorsDesign, Engineering
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #51
Last Updated2026-05-26

Introduction

Overview

Chat Thread Widget is a self-contained messaging component that composes ChatBubble, Avatar, TextArea, and Button into a complete conversation thread with send, reply, and reaction UX. It provides a ready-to-use messaging surface for SaaS applications that need in-app chat, support threads, or comment-style conversations. The existing chat-bubble.tsx handles individual message rendering; this widget adds the thread container, input area, message list, and interaction layer.

Goals

  • Deliver a <ChatThread> component that renders a scrollable message list with a compose input area.
  • Compose existing ChatBubble, Avatar, TextArea, and Button primitives.
  • Support send, reply, and emoji reaction interactions.
  • Handle empty, loading, and error states with appropriate UX.
  • Ship Storybook stories covering typical chat flows and edge cases.

Non-Goals

  • Real-time message delivery (WebSocket, SSE, or polling integration).
  • File or image attachment uploads.
  • Message search or filtering.
  • Typing indicators or read receipts beyond ChatBubble’s existing status prop.
  • End-to-end encryption or message persistence.

Scope

In Scope

AreaDescription
Component<ChatThread> composing message list + input area
Message ListScrollable container rendering ChatBubble instances with avatars
Compose InputTextArea + Button for sending new messages
ReplyReply-to-message UX showing quoted original message above compose input
ReactionsEmoji reaction picker on individual messages
StatesEmpty thread, loading messages, error loading, send failure
StoriesStorybook stories for conversations, replies, reactions, and all states

Out of Scope

AreaReason
Real-time syncApplication-layer concern; widget calls onSend callback
File attachmentsSeparate feature; compose area handles text only
Message threading (nested replies)Flat thread only; nested threading is a separate pattern
User presence / online indicatorsApplication-layer concern

Users and Pain Points

User Groups

UserDescriptionNeeds
DevelopersEngineers building SaaS apps with in-app messagingA composable chat widget using DS primitives
DesignersDesign-system consumersConsistent chat UI that matches DS visual language
End UsersSaaS application usersFamiliar, responsive messaging experience

Pain Points

UserPain PointImpact
DevelopersAssembling chat UIs from primitives (bubble, input, scroll, reactions) is complexHigh development cost, inconsistent chat experiences
End UsersAd-hoc chat UIs often lack keyboard navigation, screen reader support, and proper focus managementPoor accessibility and usability

Definitions

TermDefinition
Chat ThreadA chronological, flat list of messages between participants
Compose InputThe text input area at the bottom of the thread for writing new messages
ReplyA message that quotes and references a previous message in the thread
ReactionAn emoji response attached to a specific message

Current State

Existing Behavior

ChatBubble in chat-bubble.tsx renders individual sent/received messages with avatar, timestamp, and delivery status. There is no thread container, compose input, reply UX, or reaction system.

Current Limitations

  • No thread-level component; consumers must build scroll containers and input areas manually.
  • No reply-to-message pattern.
  • No emoji reaction support.
  • No empty/loading/error states for the thread as a whole.

Existing Workarounds

  • Developers wrap ChatBubble instances in a custom scrollable div and add their own textarea.
  • Reply and reaction UX are built from scratch in each application.

Proposed Solution

Summary

Introduce <ChatThread> that accepts a messages array and renders them as ChatBubble components in a scrollable container, with a compose input area at the bottom. The component supports onSend for new messages, onReply for reply-to interactions, and onReact for emoji reactions. State management (adding messages to the array) is the consumer’s responsibility.

Key Capabilities

  • Scrollable message list with auto-scroll to newest message.
  • Compose input with send button (Enter to send, Shift+Enter for newline).
  • Reply mode: clicking reply on a message shows the quoted message above the compose input.
  • Emoji reaction: clicking a reaction button on a message opens a picker; selected emoji is added to the message.
  • Empty thread: shows a placeholder message encouraging the first message.
  • Loading state: skeleton bubbles while messages load.
  • Error state: error banner with retry action.

User Experience

Users see a scrolling list of messages with sent messages on the right and received on the left. Each message has a subtle reaction button and reply button on hover. The compose area at the bottom has a textarea and send button. When replying, a quoted preview of the original message appears above the textarea.

Developer Experience

<ChatThread
messages={messages}
currentUserId="user-1"
onSend={(text) => sendMessage(text)}
onReply={(text, replyToId) => sendReply(text, replyToId)}
onReact={(messageId, emoji) => addReaction(messageId, emoji)}
/>

Requirements

IDRequirementPriorityNotes
FR-001ChatThread renders a scrollable list of ChatBubble messagesMust-
FR-002Compose input at the bottom sends messages via onSendMust-
FR-003Reply-to interaction shows quoted message and sends via onReplyShould-
FR-004Emoji reaction on messages fires onReactShould-
FR-005Empty, loading, and error states are handledMust-
FR-006Auto-scroll to newest message on new message arrivalMust-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001messages prop accepts ChatMessage[] with id, text, senderId, timestamp, reactions, and replyTo fieldsComprehensive message modelMust
FUNC-002Messages from currentUserId render as “sent” (right-aligned); others as “received” (left-aligned)Familiar chat layoutMust
FUNC-003Enter key sends the current message; Shift+Enter inserts a newlineStandard chat keyboard behaviorMust
FUNC-004Send button is disabled when textarea is empty or whitespace-onlyPrevents empty sendsMust
FUNC-005Clicking reply on a message enters reply mode with quoted previewThreaded conversation contextShould
FUNC-006Escape key exits reply modeQuick escape from replyShould
FUNC-007Reaction button on each message opens an emoji pickerExpress reactions without typingShould
FUNC-008Existing reactions on a message display as small badges below the bubbleVisible social signalsShould
FUNC-009Auto-scroll to bottom when a new message is added to messagesUsers see latest messagesMust
FUNC-010Error state shows a banner with optional retry buttonRecoverable error handlingMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Renders in under 100ms for threads with up to 200 messagesPerformanceMust
NFR-002All interactive elements are keyboard-navigableAccessibilityMust
NFR-003Works in light and dark themesThemingMust
NFR-004No new runtime dependencies beyond existing DS componentsMaintainabilityMust
NFR-005Compose input maintains focus after sending a messageUsabilityMust

API / Interface Requirements

Public API

NameTypeDescriptionRequired
messagesChatMessage[]Array of messages to displayYes
currentUserIdstringID of the current user (determines sent vs. received)Yes
onSend(text: string) => voidCallback when a new message is sentYes
onReply(text: string, replyToId: string) => voidCallback when replying to a messageNo
onReact(messageId: string, emoji: string) => voidCallback when adding a reactionNo
isLoadingbooleanShow loading skeletonNo
errorstringError message to displayNo
onRetry() => voidRetry callback for error stateNo
placeholderstringCompose textarea placeholder textNo
emptyMessagestringText shown when thread has no messagesNo
classNamestringAdditional CSS classesNo

ChatMessage Type

interface ChatMessageReaction {
emoji: string;
count: number;
userReacted: boolean;
}
interface ChatMessage {
id: string;
text: string;
senderId: string;
senderName: string;
senderAvatar?: string;
timestamp: string;
status?: "sending" | "sent" | "delivered" | "read" | "failed";
reactions?: ChatMessageReaction[];
replyTo?: { id: string; text: string; senderName: string };
}

Example Usage

import { ChatThread } from "@/components/ui/chat-thread";
<ChatThread
messages={[
{ id: "1", text: "Hey, how's the project going?", senderId: "user-2", senderName: "Alice", timestamp: "10:30 AM" },
{ id: "2", text: "Going well! Just finishing the design review.", senderId: "user-1", senderName: "Bob", timestamp: "10:32 AM", status: "read" },
]}
currentUserId="user-1"
onSend={(text) => console.log("Send:", text)}
/>

API Notes

  • messages should be sorted chronologically by the consumer.
  • ChatBubble is used internally; its props are derived from ChatMessage.
  • Reaction emoji picker uses a small predefined set (thumbs up, heart, laugh, surprised, sad, fire) rather than a full emoji keyboard.

Accessibility Requirements

IDRequirementNotes
A11Y-001Message list has role="log" with aria-live="polite" for new messagesNew messages announced without interrupting current task
A11Y-002Each message is a focusable region with aria-label including sender and timestampKeyboard navigation through messages
A11Y-003Compose textarea has a descriptive aria-label”Type a message” or custom placeholder
A11Y-004Reply and reaction buttons have descriptive aria-label including message contexte.g., “Reply to Alice’s message”
A11Y-005Reply preview is announced to screen readers when entering reply modeFocus moves to textarea with context
A11Y-006Emoji reaction picker is keyboard-navigableArrow keys to select, Enter to confirm, Escape to close

Checklist

  • Keyboard support is defined.
  • Focus behavior is defined.
  • Screen reader behavior is defined.
  • Color contrast requirements are met.
  • Reduced motion behavior is considered.
  • Semantic HTML expectations are documented.
  • ARIA usage is defined only where needed.

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page with overview and props tableStorybookMust
DOC-002”When to use / When not to use” guidanceStorybook docsMust
DOC-003Stories for basic conversation, reply flow, reactions, and all statesStorybookMust
DOC-004Story demonstrating keyboard navigation through threadStorybookShould

Dependencies

DependencyTypeOwnerStatusNotes
ChatBubbleEngineeringDesign SystemReadyIndividual message rendering
AvatarEngineeringDesign SystemReadySender avatars
TextAreaEngineeringDesign SystemReadyCompose input
ButtonEngineeringDesign SystemReadySend, reply, reaction buttons
BadgeEngineeringDesign SystemReadyReaction display badges
Design tokensDesignDesign SystemReady-

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Flat thread only; no nested repliesUsers expecting Discord/Slack-style threading will not find it hereDocument as intentional; nested threading is a separate widget
Predefined emoji set rather than full emoji keyboardLimited reaction expressionKeep the set small and useful; full keyboard can be a future enhancement
No file attachments in composeLimited for support-chat use casesDocument text-only scope; file attachments are a separate feature
Large threads (200+ messages) may scroll slowlyPerformance concernImplement virtual scrolling if performance testing shows degradation

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should message grouping (consecutive messages from same sender) collapse avatars?David HolmesOpen
Q-002Should the compose area support markdown formatting?David HolmesOpen
Q-003Should the emoji reaction set be configurable?David HolmesOpen
Q-004Should virtual scrolling be included in v1 for large threads?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001Messages render as ChatBubbles with correct sent/received alignmentFR-001, FUNC-002
AC-002Typing text and pressing Enter sends via onSendFR-002, FUNC-003
AC-003Shift+Enter inserts a newline without sendingFUNC-003
AC-004Reply mode shows quoted message and sends via onReplyFR-003, FUNC-005
AC-005Emoji reaction picker opens and fires onReactFR-004, FUNC-007
AC-006Empty thread shows placeholder messageFR-005
AC-007Loading state shows skeleton bubblesFR-005
AC-008Error state shows error banner with retryFUNC-010
AC-009Thread auto-scrolls to bottom on new messageFR-006
AC-010All Storybook stories render without errorsDOC-003
AC-011Component passes axe accessibility auditNFR-002

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • Compose ChatBubble for each message; do not reimplement bubble rendering.
  • Use Avatar for sender avatars, TextArea for compose input, Button for send/reply/reaction triggers.
  • Use Badge for reaction display.
  • Implement a small predefined emoji set (6 emojis) for the reaction picker.
  • Use role="log" with aria-live="polite" on the message list.
  • Place stories under the SaaS Widgets Storybook section.

LLM Should Not

  • Implement WebSocket or real-time sync.
  • Add file upload or attachment support.
  • Build nested threading.
  • Add a full emoji keyboard.
  • Modify existing ChatBubble or Avatar components.

Decision Log

DateDecisionReasonOwner
2026-05-26Flat thread only, no nested repliesKeeps complexity manageable; nested threading is architecturally differentDavid Holmes
2026-05-26Predefined 6-emoji reaction setAvoids full emoji keyboard dependency; covers common reactionsDavid Holmes
2026-05-26Consumer manages message array stateWidget is a controlled component; no internal state management for messagesDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft