Skip to content
txbrendan/ui

Message Row

beta

A message with quick actions. Pointing at it or moving focus into it springs in Reply, and Edit on your own, with R and E keys. A reply or an edit opens in place.

Preview

Point at a message, or Tab to it. R replies, E edits your own.

Team sync moves to Room 3 on Wednesday while Room 2 is repainted. Same time, same link.

  • Josh Tait Got it, thanks Anna.

Michael Reid18:10 · in Design

Spare chargers are in the cupboard. Last one out locks up, please.

Kate Clarke18:24

Can someone cover my desk on Wednesday? I'm on a late shift.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/message-row

Use it for

  • Messages in a chat, a team feed, or an activity list, stacked in a card like list rows.
  • Set own on the viewer's messages, so they get Edit. Pass me, so a new reply shows their name and photo.
  • Save what onAction hands you. The reply or edit already shows in place, and new props with it replace the local copy.
  • Give peek the author's role, team, and next event, so their name opens a card.

Avoid it for

  • A new message to a group. Use Composer, which says who it reaches.
  • Rows that are not messages, like orders or files. Use a list row.
  • Extra actions without a one-key shortcut. Each row's keys are how the keyboard reaches them fast.

Props

  • author

    Required. Who posted it. Initials show until the photo loads.

    { person: string; photo?: string } | string

  • time

    Required. When, like "18:02".

    string

  • text

    Required. The message. Line breaks show.

    string

  • own

    The viewer's own message: adds Edit, with E.

    boolean · default false

  • me

    Who is replying, so a new reply shows their name and photo.

    { person: string; photo?: string } | string · default "You"

  • context

    Where it was posted, like "in Design". It follows the time.

    string

  • replies

    Replies already there, each { author, text }.

    MessageReply[]

  • onAction

    A reply was sent or the message was edited, and it shows in place. Save it here.

    (action: "reply" | "edit", text: string) => void

  • peek

    The author's peek, like role, team, and next event. Without it, the name is plain.

    Omit<PeekCardProps, "person" | "photo">

  • extraActions

    More quick actions after Reply and Edit, like Pin. Give each a one-key shortcut.

    HoverAction[]

  • divider

    A 1px hairline under it, inset to its padding. The last row has none.

    boolean · default true

  • className

    Extra classes on the row. Later classes win.

    string

Accessibility

  • Each message is an article, named by its author and time, and a Tab stop of its own.
  • Focus on the message, or anywhere in it, shows its actions, the same as pointing at it. R replies and E edits while they show.
  • The keys stay quiet while you type in a field.
  • Reply puts the cursor in a field named Reply to {first name}. Enter sends, and Esc cancels without closing anything around it.
  • When the reply or edit closes, focus comes back to the message, so the keyboard keeps its place.
  • Replies are a list named Replies to {author}. The hairline is a border, so it stays in forced colors.

Examples

A plain message, with its time

<MessageRow author={{ person: "Anna Price" }} time="18:02" text="Team sync moves to Room 3 on Wednesday." />

Your own, with replies saved

<MessageRow
  author={me}
  me={me}
  own
  time="18:10"
  context="in Design"
  text={message.text}
  replies={message.replies}
  onAction={(action, text) => (action === "edit" ? saveEdit(message.id, text) : saveReply(message.id, text))}
/>

With a peek on the name

<MessageRow
  author={{ person: "Sam Carter" }}
  time="Mon 09:12"
  text="Demo day is Tuesday at 6."
  peek={{ subtitle: "Head of design · Design", facts: [["Next", "Design review, Tue 19:00"]] }}
/>