Message
A chat message row composing an avatar, author, timestamp, and bubble.
Overview
Message is a chat message row: an optional avatar, then a content column with an author +
timestamp header and a Bubble. The side prop flips the layout so outgoing messages align to the
end. Compose it from MessageAvatar, MessageContent, MessageAuthor, and MessageTimestamp,
with a Bubble (from @ghds/react/bubble) and an Avatar (from @ghds/react/avatar).
Anatomy
A flex row laid out as: MessageAvatar (a leading Avatar) and MessageContent — a column holding
a header (MessageAuthor + MessageTimestamp) above a Bubble. side="sent" reverses the row so
the avatar and bubble align to the end. Spacing and text colours come from @ghds/tokens
(comp.message.*).
Variants & States
The only axis is side (received aligns to the start, sent to the end). The bubble carries the
visual variant.
| State | Status | Notes |
|---|---|---|
| received | Implemented | Aligns to the start (incoming). |
| sent | Implemented | Aligns to the end (outgoing). |
| Hover / Focus | Not implemented | The row is static; interactive controls set their own states. |
| Disabled | Not implemented | — |
| Loading | Not implemented | — |
Usage
Do
- Match side to direction: received for incoming, sent for the current user.
- Pair Bubble’s variant with side (received bubble on a received row, sent on sent).
- Give MessageContent align-items: flex-end on sent rows so the header and bubble hug the edge.
Don't
- Don't hand-build message rows from raw divs — the subcomponents carry the spacing and text tokens.
- Don't convey who sent a message with colour alone; keep the author name visible.
Accessibility
- The author name (
MessageAuthor) makes the speaker explicit, so identity doesn’t rely on avatar or bubble colour alone. - Provide the
Avataranameso it has an accessible label / initials fallback. - For a full log, wrap messages in
MessageScroller(or a list/log landmark) so the sequence is conveyed to assistive technology. - See the Accessibility Guide for shared conventions.
Content
Keep the header to a short author name and a compact timestamp. The message text lives in the
Bubble; don’t repeat the author inside it.
Props API
React@ghds/react
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'received' | 'sent' | 'received' | 'received' aligns to the start; 'sent' aligns to the end (Message). |
...rest | HTMLAttributes<HTMLDivElement> | — | Native <div> attributes pass through to the row. |
MessageAvatar / MessageContent | HTMLAttributes<HTMLDivElement> | — | The avatar slot and the header + bubble column. |
MessageAuthor / MessageTimestamp | HTMLAttributes<HTMLSpanElement> | — | The author name and timestamp in the header. |
Web Components@ghds/web-components
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'received' | 'sent' | 'received' | Reflected attribute; 'received' aligns to the start, 'sent' to the end. |
slot="avatar" | slot | — | Leading avatar (e.g. <gh-avatar slot="avatar">). |
slot="author" / slot="timestamp" | slot | — | The header author name and timestamp. |
(default slot) | slot | — | The message body, typically a <gh-bubble>. |
React Native@ghds/react-native
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'received' | 'sent' | 'received' | 'received' aligns to the start; 'sent' aligns to the end (Message). |
children | ReactNode | — | Row content — compose with the Message* sub-components. |
testID | string | — | Test handle for queries (Message). |
MessageAvatar / MessageContent | ReactNode | — | The avatar slot and the header + bubble column. |
MessageAuthor / MessageTimestamp | ReactNode | — | The author name and timestamp in the header. |
Code examples
// React — @ghds/react
import {
Message,
MessageAvatar,
MessageContent,
MessageAuthor,
MessageTimestamp,
} from '@ghds/react/message';
import { Bubble } from '@ghds/react/bubble';
import { Avatar } from '@ghds/react/avatar';
<Message side="received">
<MessageAvatar>
<Avatar name="Bo Ram" />
</MessageAvatar>
<MessageContent>
<MessageAuthor>Bo Ram</MessageAuthor>
<MessageTimestamp>10:02</MessageTimestamp>
<Bubble variant="received">Hey — are we still on for tomorrow?</Bubble>
</MessageContent>
</Message>;
<!-- Web Components — @ghds/web-components -->
<script type="module">
import '@ghds/web-components/message';
import '@ghds/web-components/avatar';
import '@ghds/web-components/bubble';
</script>
<gh-message side="received">
<gh-avatar slot="avatar" name="Bo Ram"></gh-avatar>
<span slot="author">Bo Ram</span>
<span slot="timestamp">10:02</span>
<gh-bubble variant="received">Hey — are we still on for tomorrow?</gh-bubble>
</gh-message>
// React Native — @ghds/react-native
import {
Message,
MessageAvatar,
MessageContent,
MessageAuthor,
MessageTimestamp,
} from '@ghds/react-native/message';
import { Bubble } from '@ghds/react-native/bubble';
import { Avatar } from '@ghds/react-native/avatar';
<Message side="received">
<MessageAvatar>
<Avatar name="Bo Ram" />
</MessageAvatar>
<MessageContent>
<MessageAuthor>Bo Ram</MessageAuthor>
<MessageTimestamp>10:02</MessageTimestamp>
<Bubble variant="received">Hey — are we still on for tomorrow?</Bubble>
</MessageContent>
</Message>;
Live Demo
React and Web Components render live below, driven by the same tokens — toggle dark mode in the
header to see both respond identically. React Native can’t run in a browser without additional
react-native-web wiring (not set up in this site), so its usage is shown as a code sample instead.