Kai Ito
Strong on thermal design. Move Ana to the onsite.
One message: the avatar, the author's name and the time, the text, and actions. Put messages in a MessageList, which keeps the newest in view and announces new ones. It is an <article> named by its author. Native attributes and ref pass through.
import { Message } from "@etched/monet/message";
export function Preview() {
return (
<Message name="Kai Ito" time={<time dateTime="2026-10-26T09:14">9:14 AM</time>}>
Strong on thermal design. Move Ana to the onsite.
</Message>
);
}import { Message } from "@etched/monet/message";The plain variant, for notes and comments: the avatar, the author's name and the time, then the text. The actions show while the pointer is over the note or focus is in it.
import { Attachment, AttachmentGroup } from "@etched/monet/attachment";
import { Button } from "@etched/monet/button";
import { Message } from "@etched/monet/message";
export function Notes() {
return (
<div className="rows" style={{ maxInlineSize: "36rem" }}>
<Message
name="Kai Ito"
time={<time dateTime="2026-10-26T09:14">9:14 AM</time>}
actions={
<>
<Button size="sm" variant="quiet">
Edit
</Button>
<Button size="sm" variant="quiet">
Delete
</Button>
</>
}
>
<p>Strong on thermal design: she walked through the heat spreader trade-offs without notes.</p>
<p>Move her to the onsite. Sam, can you take the systems round?</p>
</Message>
<Message name="Sam Lee" time={<time dateTime="2026-10-26T09:40">9:40 AM</time>}>
<p>Yes. Here is the take-home and my rubric.</p>
<AttachmentGroup aria-label="Files from Sam Lee">
<Attachment name="take-home-ana-ruiz.pdf" size={210_000} href="#take-home-ana-ruiz.pdf" />
<Attachment name="systems-rubric.csv" size={4_200} href="#systems-rubric.csv" />
</AttachmentGroup>
</Message>
</div>
);
}variant="bubble" with align="end" is the user's own turn: at the end of the line, in a bubble, without avatar. Its name is for screen readers only. The other side stays plain; avatar takes a mark in place of initials.
import { SparkleIcon } from "@phosphor-icons/react";
import { Message } from "@etched/monet/message";
const assistant = (
<span style={{ display: "grid", placeItems: "center", inlineSize: "var(--et-avatar-sm)", blockSize: "var(--et-avatar-sm)", borderRadius: "var(--et-radius-full)", background: "var(--et-contrast)", color: "var(--et-text-inverse)" }}>
<SparkleIcon aria-hidden />
</span>
);
export function Bubbles() {
return (
<div className="rows" style={{ maxInlineSize: "36rem" }}>
<Message name="You" variant="bubble" align="end" time={<time dateTime="2026-10-26T10:02">10:02 AM</time>}>
Which candidates wait on feedback for more than two days?
</Message>
<Message name="Orbit assistant" avatar={assistant} time={<time dateTime="2026-10-26T10:02">10:02 AM</time>}>
Two: Ana Ruiz (onsite on Oct 21, waiting on Kai Ito) and José Núñez (phone screen on Oct 22, waiting on Sam Lee).
</Message>
<Message name="Mira Osei" variant="bubble" time={<time dateTime="2026-10-26T10:05">10:05 AM</time>}>
I can take José's feedback today.
</Message>
</div>
);
}article named by name through aria-label. The default avatar is hidden from screen readers, as the name is already there.align="end", the name is hidden on screen, but screen readers still hear it.loading sets aria-busy while the text is written. A MessageList announces the message when it is done.actions stay in the tab order while hidden, and show when focus is in the message.actions an aria-label. Give a custom avatar alt=""; the name already says who wrote it.| Prop | Type | Default | Description |
|---|---|---|---|
| name * | string | – | The author. It names the message, and its initials make the default avatar. |
| children * | ReactNode | – | The message: text, paragraphs, lists, or an AttachmentGroup. |
| time | ReactNode | – | When it was sent, as the app writes times: <time dateTime="2026-10-26T10:42">10:42 AM</time>. |
| avatar | ReactNode | – | In place of the initials: an <Avatar size="sm" alt=""> with a photo, or a mark for an assistant. null shows none. |
| actions | ReactNode | – | Buttons for this message at the end of its name's line, such as Copy or Edit: size="sm" ghost or quiet buttons (an icon-only one needs aria-label). They show while the pointer is over the message or focus is in it, and always on touch screens. |
| variant | "plain" | "bubble" | "plain" | |
| align | "start" | "end" | "start" | end puts the message at the end of the line, without an avatar, and shows the name to screen readers only: the user's own messages. A MessageList does not announce them, and scrolls to the newest one. |
| loading | boolean | – | Still being written, such as an assistant's answer while it streams (aria-busy). A MessageList announces it when it is done. |