Inkbox

Component

Message

One message in a conversation: a 32px avatar tile, the sender and time in 14px, the bubbles, and a 12px muted footer.

npx -y github:anwarlemu/inkbox-brand-cli#release add message
Preview
R
Ray Liao
Can your agent publish its Agent Card?
Then other organizations can find it and delegate to it.
A
@acme
Done. It is listed in the public directory now.
Delivered
Agent output
test-agent
I sent the first email from test-agent-233@inkboxmail.com and saved my identity for future sessions. The tunnel at test-agent-233.inkboxwire.com is idle until a request arrives.

Usage

Build a conversation from Message rows inside a MessageGroup: MessageAvatar with an initial or a picture, MessageContent with a MessageHeader (the name, then a time element), the Bubble pieces and an optional MessageFooter (Delivered, Read). Set align end for the person's own messages; the row mirrors to the right.

Avoid

  • Round avatars
  • A bold sender name
  • Timestamps darker than the muted purple

Exports: Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader. Read the examples for composition and props.

Example

examples/message.tsx
import { CheckIcon } from "@phosphor-icons/react/ssr";​import { Bubble, BubbleContent, BubbleGroup } from "@/components/ui/bubble";import { Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader } from "@/components/ui/message";​export function Preview() {  return (    <MessageGroup className="mx-auto w-full max-w-140">      <Message>        <MessageAvatar>R</MessageAvatar>        <MessageContent>          <MessageHeader>            Ray Liao <time dateTime="2026-08-08T19:16">7:16 PM</time>          </MessageHeader>          <BubbleGroup>            <Bubble variant="secondary">              <BubbleContent>Can your agent publish its Agent Card?</BubbleContent>            </Bubble>            <Bubble variant="secondary">              <BubbleContent>Then other organizations can find it and delegate to it.</BubbleContent>            </Bubble>          </BubbleGroup>        </MessageContent>      </Message>      <Message align="end">        <MessageAvatar>A</MessageAvatar>        <MessageContent>          <MessageHeader>            @acme <time dateTime="2026-08-08T19:18">7:18 PM</time>          </MessageHeader>          <Bubble align="end">            <BubbleContent>Done. It is listed in the public directory now.</BubbleContent>          </Bubble>          <MessageFooter>            <CheckIcon weight="bold" /> Delivered          </MessageFooter>        </MessageContent>      </Message>    </MessageGroup>  );}​export function AgentOutput() {  return (    <Message className="mx-auto max-w-140">      <MessageAvatar>        {/* eslint-disable-next-line @next/next/no-img-element */}        <img src="/octopus.png" alt="" className="bg-subtle" />      </MessageAvatar>      <MessageContent>        <MessageHeader>          test-agent <time dateTime="2026-08-08T19:20">7:20 PM</time>        </MessageHeader>        <Bubble variant="ghost">          <BubbleContent>I sent the first email from test-agent-233@inkboxmail.com and saved my identity for future sessions. The tunnel at test-agent-233.inkboxwire.com is idle until a request arrives.</BubbleContent>        </Bubble>      </MessageContent>    </Message>  );}

Source

components/ui/message.tsx
import * as React from "react";​import { cn } from "@/lib/utils";​// One message in an agent conversation (Figma 427:2715, the inbox and iMessage): a 32px avatar tile (the initial on// the avatar blue, 6px corners, as the rail's "R"), the sender's name in 14px ink with the time in 14px muted beside// it, the bubbles, and a 12px muted footer (Delivered). Outgoing messages (align end) mirror to the right.function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {  return <div data-slot="message-group" className={cn("flex min-w-0 flex-col gap-4", className)} {...props} />;}​function Message({ className, align = "start", ...props }: React.ComponentProps<"div"> & { align?: "start" | "end" }) {  return <div data-slot="message" data-align={align} className={cn("group/message relative flex w-full min-w-0 gap-2.5 font-sans text-body data-[align=end]:flex-row-reverse", className)} {...props} />;}​function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="message-avatar"      className={cn(        "flex size-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-sm bg-avatar text-body leading-tight font-medium text-white group-has-data-[slot=message-footer]/message:-translate-y-6 *:[img]:size-full *:[img]:object-cover",        className,      )}      {...props}    />  );}​function MessageContent({ className, ...props }: React.ComponentProps<"div">) {  return <div data-slot="message-content" className={cn("flex w-full min-w-0 flex-col gap-1.5 break-words group-data-[align=end]/message:items-end", className)} {...props} />;}​/** The sender and the time: "Ray Liao" in ink, "Yesterday" muted, as an inbox row. */function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="message-header"      className={cn("flex max-w-full min-w-0 items-baseline gap-2 px-1 text-body leading-tight text-fg group-data-[align=end]/message:flex-row-reverse [&_time]:text-fg-muted group-has-data-[variant=ghost]/message:px-0", className)}      {...props}    />  );}​function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="message-footer"      className={cn("flex max-w-full min-w-0 items-center gap-1 px-1 text-caption leading-tight text-fg-muted group-data-[align=end]/message:justify-end group-has-data-[variant=ghost]/message:px-0 [&_svg:not([class*='size-'])]:size-3.5", className)}      {...props}    />  );}​export { Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader };

Reference: Extrapolated from the Inkbox console (Figma 427:2715): the inbox rows (sender in ink, date muted) and the rail's initial tile.