Monet
@etched/monet 0.0.1

Message

Ported

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.

Kai Ito
Strong on thermal design. Move Ana to the onsite.
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>
  );
}

Installation

import { Message } from "@etched/monet/message";

Examples

Notes

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.

Kai Ito

Strong on thermal design: she walked through the heat spreader trade-offs without notes.

Move her to the onsite. Sam, can you take the systems round?

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>
  );
}

Bubbles

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.

You
Which candidates wait on feedback for more than two days?
Orbit assistant
Two: Ana Ruiz (onsite on Oct 21, waiting on Kai Ito) and José Núñez (phone screen on Oct 22, waiting on Sam Lee).
Mira Osei
I can take José's feedback today.
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>
  );
}

Accessibility

  • An article named by name through aria-label. The default avatar is hidden from screen readers, as the name is already there.
  • With 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.
  • The actions stay in the tab order while hidden, and show when focus is in the message.
  • Give an icon-only button in actions an aria-label. Give a custom avatar alt=""; the name already says who wrote it.

API reference

PropTypeDefaultDescription
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.
timeReactNode–When it was sent, as the app writes times: <time dateTime="2026-10-26T10:42">10:42 AM</time>.
avatarReactNode–In place of the initials: an <Avatar size="sm" alt=""> with a photo, or a mark for an assistant. null shows none.
actionsReactNode–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.
loadingboolean–Still being written, such as an assistant's answer while it streams (aria-busy). A MessageList announces it when it is done.