Skip to content

Components · Collaboration

MessageRow

One message: avatar, author, time and body, with edited, pending and failed states, reactions, replies and actions.

@rookcom/monarch-react/collaborationComponent

Reactions, replies and actions

Ana Lopez

The release notes are ready for review. Comments by Thursday, please.

Usage examples

Grouping, edited and states

Internal note and system line

Purpose

Shows one message in a conversation, comment thread or ticket reply list, with the states real messaging needs: grouped follow-ups, sending, failed with retry, edited, deleted, internal notes and system lines.

Use when

  • Chat channels, direct messages and threads.
  • Ticket and request conversations, including internal notes.
  • Comment threads on records, inside a MessageTimeline or any list.

Avoid when

  • A feed of system events with verbs and objects ("Ana changed the status"): use ActivityTimeline.
  • A notification that leads elsewhere: use NotificationCenter.

Design principles

  • The body is the product’s: Monarch lays out the row and never parses or sanitises message content.
  • Every message is named by its author and time, even when grouped, so a screen reader never hears an anonymous line.
  • Actions appear on hover and whenever focus is inside the row, so the keyboard reaches them without a hidden mode; touch screens always show them.
  • Failure is recoverable in place: the error, Retry and Discard sit under the message that failed.

API reference

import { MessageRow } from '@rookcom/monarch-react/collaboration';
ExportKindRenderingRoot class
MessageRowcomponentclient.mn-message-row

The ref is forwarded to the root <article>.

messages translates every built-in string; replies and reaction are functions for plurals.

Generated from the TypeScript declarations of packages/react/src/components/collaboration/message-row.

Properties

MessageRow

One message in a conversation: avatar, author, time and body, with edited, pending, failed and deleted states, reactions, a thread reply count and per-message actions. Consecutive messages from one author are shown `grouped`.

PropTypeDescription
author requiredMessageAuthor
time requiredDateInputWhen the message was sent. A Date, epoch milliseconds or an ISO 8601 string.
actionsReactNodePer-message controls (react, reply, edit, more). Rendered in a group that appears on hover and whenever focus is inside the row, so it is reachable with Tab; always visible on touch screens.
avatarReactNodeReplaces the avatar, for example an app or bot glyph.
childrenReactNodeThe message body: text, rich content, attachments. Monarch does not sanitise HTML; the product does.
classNamestring
deletedbooleanReplaces the body with the deleted placeholder and hides reactions and actions.
editedbooleanShows the edited marker.
errorReactNodeWhy sending failed. Default `messages.failed`.
footerReactNodeContent below the body: attachments, link previews, read receipts.
groupedbooleanA follow-up from the same author: hides the avatar and header and shows the time in the gutter on hover and focus. The author name stays in the accessible name. MessageTimeline sets this for you.
highlightedbooleanBriefly highlights the row, for example after a deep link.
idstring
localestring
messagesPartial<MessageRowMessages>Translations of the built-in text.
nowDateInputReference time for the relative timestamp. Pass it for identical server and client output.
onDiscard(() => void)
onOpenThread(() => void)
onReactionToggle((emoji: string) => void)Called with the emoji when a reaction chip is pressed. Without it the chips are read-only.
onRetry(() => void)
onShowEdits(() => void)Makes the edited marker a button that opens the edit history.
reactionActionReactNodeExtra control after the reaction chips, typically an "Add reaction" button.
reactionsreadonly MessageReaction[]
replyCountnumberThread reply count. Shown as a button when `onOpenThread` is set.
statusMessageRowStatusDelivery state. `pending` dims the body; `failed` shows the error with Retry and Discard.
tagsReactNodeSmall tags after the author name: audience ("Internal"), role ("Guest", "App"), pinned.
timeDisplay"relative" | "clock"`relative` ("5 minutes ago", default) or `clock` ("14:05"). Both render a `<time datetime>` with the full date in `title`.
timeZonestring
variantMessageRowVariant`internal` tints the row for notes only teammates can see (with a tag in `tags`); `system` is a centred, compact line for server-written events.

Variants

PropValuesDescription
variant'default''internal''system'internal tints the row and draws a warning edge for teammate-only notes (add an audience tag in tags); system is a centred one-line event.
grouped'false''true'Follow-up from the same author: no avatar or header, the time appears in the gutter on hover and focus.
timeDisplay'relative''clock'“5 minutes ago” (RelativeTime) or “14:05”. Both carry the full date in title.

States

StateExposed asBehaviour
Pendingdata-status="pending"Body muted, “Sending…” in a status region, actions hidden, aria-busy.
Faileddata-status="failed"The error (or “Not sent”) in an alert, with Retry and Discard when their callbacks are given.
Edited–“(edited)” after the body; a button when onShowEdits opens the history.
Deleteddata-deleted“This message was deleted.” replaces the body; reactions, footer and actions are dropped.
Highlighteddata-highlightedAccent tint for a deep-linked or searched message.

Interaction behaviour

  • Reaction chips are toggle buttons when onReactionToggle is set, otherwise read-only.
  • The reply count calls onOpenThread.
  • Retry and Discard call onRetry and onDiscard; the product resends or removes the message.
  • The actions group becomes visible on pointer hover and whenever focus enters the row.

Keyboard support

KeysAction
TabMoves through the row’s controls: edited marker, reactions, replies, then the actions.
Enter / SpaceActivates the focused reaction, reply count or action.

The row itself is not a tab stop; only its controls are.

Accessibility

Role
article
Accessible name
Labelled by the author name and the time (aria-labelledby), also when grouped.
  • Grouped rows keep a visually hidden author name.
  • Reaction chips use aria-pressed and a spoken name such as “thumbs up, 3”; pass label for each emoji.
  • The time is a <time datetime> with the full date in title.
  • Pending text is a polite status; a failure is an alert so the sender hears it.
  • Actions sit in a named group (“Message actions”).

Responsive behaviour

  • Header items wrap under the name on narrow screens; long words in the body break.
  • On touch screens (hover: none) the actions stay visible at the end of the row and the grouped time is shown.

Theme support

  • Hover and focus use bg-subtle, internal notes warning-subtle with a warning-border edge, highlight accent-subtle.
  • Pressed reactions use the accent tokens; failed text danger-strong. Forced colours outline the highlighted row.

Do and don't

Do

  • Render rows inside MessageTimeline, which sets grouped for you.
  • Give every reaction a spoken label and a title listing who reacted.
  • Put the audience (“Internal”, “Reply to customer”) in tags as text, not colour alone.

Don't

  • Do not put unsanitised HTML in the body; sanitise in the product.
  • Do not hide important actions only in the hover group; keep primary actions such as Reply reachable elsewhere too on small screens.
  • Do not use the system variant for user-written content.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsAvatar, RelativeTime

Testing coverage

  • Names the article by author and time, also when grouped, and renders a <time datetime> with the full date.
  • Edited marker, and the edit-history button when onShowEdits is set.
  • Pending status, failed alert with Retry and Discard, default failure text.
  • Reaction toggles with aria-pressed and spoken counts; read-only reactions.
  • Thread count, keyboard-reachable actions group, deleted placeholder.
  • Internal, highlighted, system and clock variants; translated text; invalid time.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery/message-row; Cross-browser axe: /components/message-row

Tests: packages/react/src/components/collaboration/message-row