On this page
Import
import { Message, MessageList } from '@envsemble/envui';
Usage
A MessageList holds the conversation, oldest first, and each Message in it takes from:
selffor the person using the app, a compact bubble at the inline end; your own bubble grows to three quarters of the list, and to at least 16 rem in a narrow list.otherfor anyone else, such as an assistant or another participant, full-width content with no bubble.- Never set
from="self"on someone else's message to put it on a side.
author names who wrote it, meta holds supporting facts beside the author, such as a RelativeTime, and children the message: plain text keeps its line breaks, and formatted content, such as Markdown, fills the width of a message from someone else.
import { useEffect, useRef, useState } from 'react';
import { Composer, Message, MessageList, Column } from '@envsemble/envui';
interface Entry {
id: number;
from: 'self' | 'other';
text: string;
}
const answer =
'Noted. I added it to the launch checklist and will remind Sam Lee on Friday, before the review.';
export default function Conversation() {
const [messages, setMessages] = useState<Entry[]>([
{ id: 1, from: 'self', text: 'What is still open for the launch?' },
{
id: 2,
from: 'other',
text:
'Three items: the pricing page, the press kit and the support rota. ' +
'Sam Lee owns the first two.',
},
]);
const [replying, setReplying] = useState(false);
const timer = useRef<number>(undefined);
useEffect(() => () => window.clearInterval(timer.current), []);
function stop() {
window.clearInterval(timer.current);
setReplying(false);
}
function send(text: string) {
const id = Date.now();
const words = answer.split(' ');
let shown = 1;
setMessages((list) => [
...list,
{ id, from: 'self', text },
{ id: id + 1, from: 'other', text: words[0] },
]);
setReplying(true);
// A timer stands in for a reply that streams in word by word.
timer.current = window.setInterval(() => {
shown += 1;
const reply = words.slice(0, shown).join(' ');
setMessages((list) =>
list.map((entry) => (entry.id === id + 1 ? { ...entry, text: reply } : entry)),
);
if (shown === words.length) stop();
}, 120);
}
return (
<Column gap={5}>
<MessageList aria-label="Conversation with the assistant" busy={replying}>
{messages.map((entry) => (
<Message
key={entry.id}
from={entry.from}
author={entry.from === 'other' ? 'Assistant' : undefined}
>
{entry.text}
</Message>
))}
</MessageList>
<Composer
aria-label="Message the assistant"
placeholder="Ask anything"
onSend={send}
running={replying}
onStop={stop}
/>
</Column>
);
}Actions and status
actions holds small actions below the message, such as Copy or Try again: buttons without a variant or size of their own are small ghost buttons. status is sending, with a spinner and Sending, or failed, with an error icon and Not sent; statusLabel replaces the words.
<Message
from="self"
meta="09:41"
status={status}
actions={
status === 'failed' && (
<Button variant="text" onClick={retry}>
Try again
</Button>
)
}
>
{`Northwind Hall
12 Harbour Street`}
</Message>
Streaming
busy on MessageList shows a busy row after the last message, where the reply will show: busyAuthor, who the reply comes from, as a message's author, then a quiet spinner and Working in the muted color (busyLabel, or messages.message.working). It reports aria-busy while a reply is on its way or streams in, until it is cleared. The list doesn't scroll by itself: keep the newest message in view yourself, without moving focus.
<MessageList aria-label="Conversation with Sam Lee" busy={streaming} busyAuthor="Assistant">
…
</MessageList>
Composition
skeleton shows a message as its own skeleton while it loads. Message and MessageList can render in React Server Components.
Customize
The list is env-message-list, with env-message-list-busy for its busy row, and each message env-message, with its author, env-message-author, and meta, env-message-meta, in its header, then its content, env-message-content, its actions, env-message-actions, and its status, env-message-status.
Accessibility
You provide
- A name for the list, such as Conversation with Sam Lee.
busywhile a reply streams in.- Names for icon-only actions, and Try again for a failed message.
- Scrolling, without moving focus.
envUI provides
- MessageList as a polite log, and
aria-busywhilebusy, with the busy row shown but not read. - Each message as an
<article>, with its author and meta in a header. - The status as text beside its icon.
Props
Message
One entry of a conversation, such as a chat message or a comment, as an article: a compact bubble at the inline end for your own messages, full-width content for others', with an optional author, meta, status and actions.
| Prop | Type | Description |
|---|---|---|
fromrequired | 'self''other' | self: written by the person using the app, a compact bubble at the inline end. other: from anyone else, such as an assistant or another participant, full-width content without a bubble. |
author | ReactNode | Who wrote the message, such as a name, shown above it at weight 500. Leave it out where the conversation makes it clear, such as on your own messages in a conversation with one person. |
meta | ReactNode | Supporting facts beside the author, in the muted color, such as the time or a RelativeTime. |
actions | ReactNode | Small actions below the message, such as Copy or Try again: buttons without a variant or size of their own are ghost buttons at the small control height, with the touch height on touch screens; at the inline end for your own messages. A text IconButton at the edge lines its icon up with the message. false and null count as absent. |
status | 'sending''failed' | A message on its way, sending, with a spinner and Sending; or one that could not be sent, failed, with an error icon and Not sent in the danger tone and, on your own message, a red outline around the bubble. Without it the message is sent. |
statusLabelDefault 'Sending' or 'Not sent' | string | The text beside the status icon, such as "Could not deliver". It overrides EnvUIProvider's `messages.message.sending` and `.failed`. |
childrenrequired | ReactNode | The message. Plain text keeps its line breaks; formatted content, such as Markdown, fills the width of a message from someone else. |
skeletonDefault false | boolean | Shows it and its content as a busy skeleton while that loads; turning it on or off renders the content anew. |
Also accepts the native props of an HTML element, such as ref, className and aria-* attributes.
MessageList
The messages of a conversation in order, as a log: assistive technology politely announces messages added anywhere in it and text that changes. Set busy while a reply is on its way or streams in. Name it with aria-label or aria-labelledby.
| Prop | Type | Description |
|---|---|---|
busyDefault false | boolean | A reply is on its way or streaming in: after the last message, where the reply will show, a row with the busy author, a quiet spinner and Working in the muted color; and the log reports aria-busy, so assistive technology can wait for the reply to finish instead of announcing every step. |
busyAuthor | ReactNode | Who the reply on its way comes from, such as Agent, named on the busy row as a message's author. |
busyLabelDefault 'Working' | string | The text beside the busy spinner, such as "Thinking". It overrides EnvUIProvider's `messages.message.working`. |
Also accepts the native props of <div>, such as ref, className and aria-* attributes.
Part names
Each part's class and state attributes are public and versioned, for an application's CSS to style.
Message
env-message- One message in a conversation.
env-message-actions- The message's actions.
env-message-author- The author's name.
env-message-content- The message's content.
env-message-header- The row with the author and the meta.
env-message-meta- The time or another fact about the message.
env-message-status- The sending or failed status.
data-env-skeleton- Any value, or present on env-message, env-message-author, env-message-header, env-message-meta
data-fromother·selfdata-statusfailed·sendingdata-text- Present on env-message-content
MessageList
env-message-list- The list of messages.
env-message-list-busy- The row after the last message while busy: the busy author, a spinner and Working.
Strings
EnvUIProvider's messages overrides each, and a component's own label prop wins over it.
messages.message.sending"Sending"The status of a message on its way.messages.message.failed"Not sent"The status of a message that could not be sent.messages.message.working"Working"Beside the spinner of a MessageList while a reply is on its way.
Changelog
- 1.0.0
Buttons in
actionsare small ghost buttons unless they set their own variant or size, and the statuses come frommessages.message. The page ismessage, after the component, instead ofmessages. Message and MessageList can render in React Server Components.- 1.0.0-prerelease.6
The busy row names who replies,
busyAuthor, and says Working beside its spinner,busyLabelormessages.message.working.While
busy, MessageList shows a quiet spinner in the muted color after the last message,env-message-list-busy.