Chat
The Supporter Response building blocks: contact avatars, the profile photo and rows, tags, message bubbles, journey links, timeline events, the reply composer and contact details. Colors come from Colors/chat.
Figma: 10 / Components / ChatBSBrandon shared their mission story inYou are not alone
I lost my grandmother to COPD and want to ensure future generations don’t suffer.
Brandon is waiting for a response
Usage
Source: src/components/ui/chat.tsx
import { MessageBubble, ShareIndicators, ThreadMessage, TimelineEvent } from '@/components/ui/chat'<ThreadMessage name='Brandon' initials='BS' src={brandon.photo} context='shared their mission story in' journey='You are not alone'>
<MessageBubble>I lost my grandmother to COPD and want to ensure future generations don’t suffer.</MessageBubble>
</ThreadMessage>
<TimelineEvent kind='waiting' name='Brandon' trailing={<ShareIndicators />}>is waiting for a response</TimelineEvent>ContactAvatar
A 40px avatar on Colors/surface/muted with the mission (heart) and high-intent (rocket) indicators above it. Pass src for an approved photo.
BSGLJM
ContactPhoto
The supporter as a small print at the top of the supporter profile: 96px wide in a 4:5 frame, tilted 2° to the left, with a 4px raised frame and shadow-photo, so the person comes first. Without a photo the initials show.
BSJM
Tags
Default and AI suggested tags, each with an optional remove. Suggested tags keep the dashed Colors/chat/ai/accent border; selecting one adds it.
Host EventMonthly Donor
Message bubbles
One bubble per direction, Roboto 16 / 24 with 16px corners. Incoming has a border; outgoing squares its top left corner.
I lost my grandmother to COPD and want to ensure future generations don’t suffer.
I lost my grandmother to COPD and want to ensure future generations don’t suffer.
Thread details
JourneyLink belongs with message context; ContactGroupHeader groups the contact list by day.
You are not alone
Yesterday5
Contact rows
Two-line rows in default, unread (medium summary and a dot), selected (Colors/chat/row/selected) and done (Colors/surface/subtle). Select a row to try it.
Timeline events
Status lines in the conversation. The AI draft uses the GoodUnited AI sparkle.
Brandon is waiting for a response
GoodUnited AI drafted a response for Brandon
You replied · 12:36 AM, Aug 16, 2026
You copied the response and replied via email
Marked as done
Reply channels
Messenger or email for the reply. Email switches the composer to copy-and-open.
Reply composer
The drafted reply in the composer gradient frame, the writing-style and feedback footer, and the already-sent guard. Send, then Write another.
Reply composer, email
channel email adds the subject and ends with Copy and open email.
Contact details
One line each for the location, the Messenger name and the email in the supporter profile.
Location: Portland, Oregon
Messenger: Brandon Smith
Email: brandon@example.com
When to use
When to reach for it, and when to use something else.
Use it for
Complete: Supporter Response screens, composed into ContactList, ConversationPane and SupporterProfile (Blocks / Supporter Response).
Complete: Incoming bubbles for what supporters shared, outgoing bubbles for replies that went out.
Complete: The suggested tag kind only for tags GoodUnited AI proposed.
Complete: ContactPhoto once per screen, for the person the conversation is with; ContactAvatar everywhere else.
Avoid
Not complete: The Colors/chat tokens outside Supporter Response; use the status and surface tokens elsewhere.
Not complete: A second composer on the same conversation; show SentNotice once a reply went out.
Not complete: Straightening, enlarging or recoloring ContactPhoto, or using it in lists and rows.
ContactPhoto
| Prop | Type | Default | Description |
|---|---|---|---|
| initials | string | – | Shown when there is no photo, or until it loads. |
| src | string | – | A photo the supporter shared. Cropped to 4:5. |
| alt | string | '' | Leave empty when the name sits beside the photo. |
ContactRow
| Prop | Type | Default | Description |
|---|---|---|---|
| initials | string | – | Shown in the ContactAvatar. |
| summary | ReactNode | – | Name and topic, Roboto / text-sm. |
| preview | ReactNode | – | The latest message, one line, truncated. |
| time | string | – | Roboto / text-xs at the right. Leave it out on done rows. |
| state | 'default' | 'unread' | 'selected' | 'done' | 'default' | Background and text weight. |
| mission, intent | boolean | true | The avatar indicators. |
| onToggleDone | () => void | – | Adds a 36px square button at the right on hover or keyboard focus: ChatDoneIcon marks the conversation as done, ChatOpenIcon as undone. It sits beside the row button, never inside it. |
| done | boolean | false | Which of the two the button offers. |
ChatTag
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | – | The tag name. |
| kind | 'default' | 'suggested' | 'default' | Suggested adds the dashed AI border. |
| onSelect | () => void | – | Makes the tag a button, used to accept a suggestion. |
| onRemove | () => void | – | Shows the remove x. |
TimelineEvent
| Prop | Type | Default | Description |
|---|---|---|---|
| kind | 'waiting' | 'ai-draft' | 'replied' | 'email-replied' | 'done' | – | The icon in its 24px circle, sitting on the thread line. |
| name | string | – | Shown first in Colors/text/primary. |
| trailing | ReactNode | – | After the text, for example ShareIndicators. |
ThreadMessage
| Prop | Type | Default | Description |
|---|---|---|---|
| name, context | string, ReactNode | – | The sender in Colors/text/primary, then what they shared in Colors/text/secondary. |
| initials, src | string | – | The 24px avatar that starts the thread line. Without initials, a person icon in a circle. |
| journey | string | – | The JourneyLink pill. |
ReplyComposer
| Prop | Type | Default | Description |
|---|---|---|---|
| channel | 'messenger' | 'email' | – | Messenger ends with Send; email adds the subject and Copy and open email. |
| message, onMessageChange | string, (value: string) => void | – | The drafted reply. Send is disabled while it is empty. |
| subject, onSubjectChange | string, (value: string) => void | – | Email only. |
| onSend, onCopyEmail | () => void | – | The primary action for each channel. |
ComposerFooter
| Prop | Type | Default | Description |
|---|---|---|---|
| onWritingStyle | () => void | – | Opens the writing-style settings. |
| feedback, onFeedback | 'up' | 'down' | null | – | Thumbs up and down on the draft; pressing again clears it. |