tsimport {XDSChatMessageMetadata} from '@xds/core/Chat'
| Guidance | Practices |
|---|---|
| Do | Compose messages using MessageList > Message > Bubble for consistent sender-aware styling and density. |
| Do | Set the density prop to control spacing globally — compact for sidebars, balanced for most views, spacious for long-form reading. Individual messages can override. |
| Do | Use the group prop on bubbles (first, middle, last) when a single sender sends multiple consecutive messages — it tightens corner radius to visually connect them. |
| Do | Use XDSChatSystemMessage with variant="divider" for date separators and default for inline status notices like joins, leaves, or topic changes. |
| Do | Put name on the first bubble and metadata on the last bubble in a message so they align with the bubble's inline padding. |
| Do | Provide an emptyState prop so new users see a clear prompt to start a conversation instead of a blank screen. |
| Do | Use the ghost bubble variant for AI-style responses that show rich content like code blocks or markdown without a visible boundary. |
| Don't | Don't use XDSChatSystemMessage for sender content — it has no avatar, alignment, or bubble. Use XDSChatMessage with a sender role instead. |
| Don't | Don't put long or multi-line content in a system message — keep it to a single short sentence. If you need more, use a bubble or a card. |
| Don't | Don't nest XDSChatMessage inside another XDSChatMessage — each message is a standalone article element with its own sender context. |
| Don't | Don't apply a fixed height directly on the message list — wrap it in a sized container and let the list fill with flex: 1. |
| Don't | Don't mix filled and ghost bubble variants within the same sender's messages — pick one style per side and use it consistently. |
| Don't | Don't place metadata or names on both the bubble and the message wrapper — pick one based on whether the content has a bubble boundary. |
tsx'use client';import {XDSChatMessageList,XDSChatMessage,XDSChatMessageBubble,XDSChatMessageMetadata,} from '@xds/core/Chat';import {XDSTimestamp} from '@xds/core/Timestamp';import {XDSButton} from '@xds/core/Button';import {XDSIcon} from '@xds/core/Icon';import {XDSText} from '@xds/core/Text';import {XDSHStack} from '@xds/core/Layout';import {ClipboardDocumentIcon,ArrowPathIcon,HandThumbUpIcon,HandThumbDownIcon,} from '@heroicons/react/24/outline';export default function ChatMessageMetadataFooter() {return (<XDSChatMessageList><XDSChatMessage sender="user"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-04-29T09:41:00" format="time" />}status="read"/>}>Summarize the Q1 revenue report.</XDSChatMessageBubble></XDSChatMessage><XDSChatMessage sender="assistant"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-04-29T09:42:00" format="time" />}footer={<XDSHStack gap={1}><XDSButtonlabel="Copy"variant="ghost"size="sm"icon={<XDSIcon icon={ClipboardDocumentIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSButtonlabel="Retry"variant="ghost"size="sm"icon={<XDSIcon icon={ArrowPathIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSButtonlabel="Good response"variant="ghost"size="sm"icon={<XDSIcon icon={HandThumbUpIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSButtonlabel="Bad response"variant="ghost"size="sm"icon={<XDSIcon icon={HandThumbDownIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSText type="supporting" color="secondary">GPT-4o</XDSText></XDSHStack>}/>}>Q1 revenue reached $2.4B, up 18% year-over-year. Enterprisesubscriptions drove 62% of the growth, while ad revenue held steady at$890M.</XDSChatMessageBubble></XDSChatMessage></XDSChatMessageList>);}
tsx'use client';import {XDSChatMessageList,XDSChatMessage,XDSChatMessageBubble,XDSChatMessageMetadata,} from '@xds/core/Chat';import {XDSTimestamp} from '@xds/core/Timestamp';const STATUSES = [{status: 'sending' as const, text: 'Deploying the update now…'},{status: 'sent' as const, text: 'Config pushed to staging.'},{status: 'delivered' as const, text: 'Verified on the staging cluster.'},{status: 'read' as const, text: 'Looks good — promoting to prod.'},{status: 'error' as const, text: 'Rollback triggered, checking logs.'},];export default function ChatMessageMetadataStatus() {return (<XDSChatMessageList>{STATUSES.map(({status, text}) => (<XDSChatMessage key={status} sender="user"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-04-29T10:15:00" format="time" />}status={status}/>}>{text}</XDSChatMessageBubble></XDSChatMessage>))}</XDSChatMessageList>);}
tsx'use client';import {XDSChatMessageList,XDSChatMessage,XDSChatMessageBubble,XDSChatMessageMetadata,} from '@xds/core/Chat';import {XDSTimestamp} from '@xds/core/Timestamp';export default function ChatMessageMetadataTimestamp() {return (<XDSChatMessageList><XDSChatMessage sender="user"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-04-29T14:30:00" format="time" />}/>}>Thanks — any blockers I should know about?</XDSChatMessageBubble></XDSChatMessage><XDSChatMessage sender="assistant"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-04-28T16:45:00" format="relative" />}/>}>Relative timestamps work too — helpful for older messages where theexact time matters less than recency.</XDSChatMessageBubble></XDSChatMessage></XDSChatMessageList>);}
tsx'use client';import * as stylex from '@stylexjs/stylex';import {XDSChatMessageList,XDSChatMessage,XDSChatMessageBubble,XDSChatMessageMetadata,} from '@xds/core/Chat';import {XDSTimestamp} from '@xds/core/Timestamp';import {XDSText} from '@xds/core/Text';import {XDSButton} from '@xds/core/Button';import {XDSIcon} from '@xds/core/Icon';import {XDSHStack, XDSVStack} from '@xds/core/Layout';import {ClipboardDocumentIcon,ArrowPathIcon,HandThumbUpIcon,HandThumbDownIcon,} from '@heroicons/react/24/outline';const styles = stylex.create({container: {maxWidth: 600,},});export default function ChatMessageMetadataShowcase() {return (<XDSVStack xstyle={styles.container}><XDSChatMessageList><XDSChatMessage sender="assistant"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-03-15T14:30:00" format="time" />}status="error"footer={<XDSHStack gap={1}><XDSButtonlabel="Retry"variant="ghost"size="sm"icon={<XDSIcon icon={ArrowPathIcon} size="sm" />}isIconOnlyonClick={() => {}}/></XDSHStack>}/>}>Sorry, something went wrong on my end.</XDSChatMessageBubble></XDSChatMessage><XDSChatMessage sender="user"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-03-15T14:31:00" format="time" />}status="read"/>}>No worries — try again with just the last 24 hours of logs.</XDSChatMessageBubble></XDSChatMessage><XDSChatMessage sender="assistant"><XDSChatMessageBubblemetadata={<XDSChatMessageMetadatatimestamp={<XDSTimestamp value="2026-03-15T14:32:00" format="time" />}footer={<XDSHStack gap={1}><XDSButtonlabel="Copy"variant="ghost"size="sm"icon={<XDSIcon icon={ClipboardDocumentIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSButtonlabel="Retry"variant="ghost"size="sm"icon={<XDSIcon icon={ArrowPathIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSButtonlabel="Good response"variant="ghost"size="sm"icon={<XDSIcon icon={HandThumbUpIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSButtonlabel="Bad response"variant="ghost"size="sm"icon={<XDSIcon icon={HandThumbDownIcon} size="sm" />}isIconOnlyonClick={() => {}}/><XDSText type="supporting" color="secondary">Claude Opus 4.6</XDSText></XDSHStack>}/>}>The canary at 11:42 AM caused a memory spike. Rolled backat 11:58 AM.</XDSChatMessageBubble></XDSChatMessage></XDSChatMessageList></XDSVStack>);}