ChatMessage
A chat message with user or assistant alignment and optional attachments.
Installation
Import from the Reflow workspace package.
Conversation
Open demoimport { ChatMessage, Button, Icon } from '@rtzll/reflow'
import { type Handle, on } from 'remix/component'
export function Example(handle: Handle) {
const messages = [
{
"role": "user",
"time": "Wednesday 6:06 PM",
"text": "What does “good design” actually mean? Everyone says it, no one defines it."
},
{
"role": "assistant",
"text": "Good design is mostly invisible — you only notice it when it’s missing. It’s less about how something looks and more about how effortlessly it lets you do what you came to do."
},
{
"role": "user",
"time": "Wednesday 6:07 PM",
"text": "So function over form?"
},
{
"role": "assistant",
"text": "Not quite. Form is part of function — something that feels good to use is, in a real sense, working better. The split between the two is mostly a myth."
},
{
"role": "user",
"time": "Wednesday 6:08 PM",
"text": "That reframes it completely."
}
] as const
let copied = -1
const icon = <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6h16M4 12h16M4 18h16" /></svg>
let index = 0
const selectSlide = (value: number) => { index = value; handle.update() }
return () => (
<>
<div style={{ display: 'grid', gap: '8px', width: 'min(100%, 568px)' }}>
{messages.map(({ role, text, ...details }, index) => (
<ChatMessage key={index} role={role} animate={false} {...details} actions={<>
<Button size="icon-compact" variant="ghost" aria-label={copied === index ? 'Copied message' : 'Copy message'}
mix={on('click', async () => {
try {
await navigator.clipboard.writeText(text)
copied = index
handle.update()
} catch { /* Clipboard unavailable */ }
})}>
<Icon name={copied === index ? 'check' : 'copy'} size={13} />
</Button>
<Button size="icon-compact" variant="ghost" aria-label={role === 'user' ? 'Edit message' : 'Regenerate response'}>
<Icon name={role === 'user' ? 'pencil' : 'rotate-ccw'} size={13} />
</Button>
</>}>{text}</ChatMessage>
))}
</div>
</>
)
}Roles
import { ChatMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<ChatMessage role="user" animate={false}>Right-aligned accent bubble.</ChatMessage>
<ChatMessage role="assistant" animate={false}>Left-aligned, no background.</ChatMessage>
</>
)
}Attachments
import { ChatMessage } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const avatar = "data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 160 160\"%3E%3Crect width=\"160\" height=\"160\" fill=\"%236b97ff\"/%3E%3Ccircle cx=\"80\" cy=\"62\" r=\"27\" fill=\"%23e1eaff\"/%3E%3Cpath fill=\"%23e1eaff\" d=\"M24 160a56 56 0 0 1 112 0z\"/%3E%3C/svg%3E"
return () => (
<>
<ChatMessage role="user" animate={false} attachments={[{ name: 'avatar.svg', previewUrl: avatar }]}>
Can you use this as my new avatar?
</ChatMessage>
<ChatMessage role="assistant" animate={false}>Looks great — it’ll crop cleanly into the circle.</ChatMessage>
</>
)
}API Reference
Public props for the Reflow implementation. Native controls also accept their standard HTML attributes and Remix event mixes.
ChatMessage
| Prop | Type | Default | Description |
|---|---|---|---|
transitionId | string | — | Matches a sent message to its originating queued message for a shared transition. |
role* | "user" | "assistant" | — | Position and treatment of the chat message. |
children | RemixNode | — | Content rendered inside the component. |
attachments | readonly ChatAttachment[] | — | Files displayed before the message body. |
label | string | — | Accessible name of the message. |
size | "default" | "compact" | — | Control density or icon-button dimensions. |
time | string | — | Timestamp displayed below a user message. |
actions | RemixNode | — | Actions displayed beside the message or composer. |
animate | boolean | true | Enables entry animation. |
ChatAttachment
| Prop | Type | Default | Description |
|---|---|---|---|
name* | string | — | Visible filename of the attachment. |
url | string | — | URL opened when the attachment is activated. |
detail | string | — | File type, size, or other secondary information. |
type | string | — | Single or multiple expansion mode. |
previewUrl | string | — | Image URL used for a visual attachment preview. |