Skip to content
Glint UI

MessageRow

The frame around a turn — the avatar gutter, the author-and-timestamp header, and the footer a turn's actions sit in.

Usage

import { MessageBubble, MessageRole } from "@glint/components/message-bubble.slint";
import { MessageRow } from "@glint/components/message-row.slint";
export component AppWindow inherits Window {
VerticalLayout {
MessageRow {
role: MessageRole.agent;
author: "Ada";
timestamp: "09:41";
avatar-fallback: "AL";
MessageBubble {
role: MessageRole.agent;
text: "Here's the diff.";
}
}
}
}

MessageBubble is the surface a turn is said on; this is everything around it. The two take the same role rather than the row taking an alignment of its own, so the frame and the surface inside it cannot disagree about which side a turn is on — one axis, spelled once at each call site.

Slint gives a component one @children, and the row spends it on the content column: the header and the avatar are properties, and what you slot in is the bubble, optionally followed by a footer row. Both gutters are reserved whichever one the avatar is standing in, which is what keeps a user turn and an agent turn the same width.

There is no MessageGroup. A parent can neither inspect nor rewrite its children in Slint, so a run of turns from one sender is grouped: true on every row after the first, set by you.

Examples

Two columns of turns

role puts the avatar in the author’s own gutter and hugs the content column to that side, so a transcript reads as two columns. The header stays at the start of the content column either way: a right-aligned name over a right-aligned bubble reads as part of the message rather than as who said it.

Continuation rows

grouped: true is the second and later turns of a run from one sender: the avatar and the header go, the gutter stays, and the row sits tighter under its predecessor — so the run reads as one block, still flush with the turn that introduced it.

Slot a second element after the bubble and it becomes the row’s footer — the actions or the delivery status under a turn. The avatar sits level with the bubble rather than with the actions below it, and Slint gives a component no way to measure a slotted child, so footer-height is the call site’s to state: it is how far the row lifts the avatar off the bottom.

has-hover is the row noticing the pointer without taking it, so footer actions can come forward on hover without each of them owning a TouchArea. Bring them forward rather than making them appear: a control faded to nothing is still in the tab order, and a keyboard user would land on something nobody can see.

Capping the bubble against the content column

content-max-width is the row less both gutters — what a slotted bubble should cap against, rather than against the whole transcript. Bind max-surface-width to it and the surface stops exactly where the content column does, whatever the avatar size and the gutter add up to.

API Reference

Properties

PropertyTypeDefaultDescription
rolein MessageRoleMessageRole.agentWho authored the turn — the agent's frame keeps to the leading side, the user's to the trailing one. The same property the bubble takes.
avatar-sourcein imageno defaultThe author's picture, and the initials shown while there is none.
avatar-fallbackin stringno default
avatar-sizein length32pxThe avatar's diameter, and the gap between it and the content column.
gutterin lengthTokens.spacing-sm
groupedin boolfalseA continuation of the turn above: the gutter stays, the avatar and the header go, and the row sits tighter under its predecessor.
authorin stringno defaultThe header, leading the content column. An empty author leaves the header out even on a row that is not a continuation.
timestampin stringno defaultTrails the author, in the muted foreground. Consumer data — Glint formats no times.
footer-heightin length0pxHow tall the footer slotted under the bubble is. The avatar sits level with the bubble rather than with the actions below it, and Slint gives a component no way to measure a slotted child — so the call site states it. Zero is a row with no footer.
content-max-widthout lengthno defaultHow wide the content column is: the row less both gutters. A slotted bubble caps against this rather than against the whole transcript.
has-hoverout boolno defaultWhether the pointer is anywhere on the row, so a consumer's footer actions can appear on hover without each of them owning a TouchArea.

Enums

EnumValues
MessageRoleuser, agent

Accessibility

  • The row is presentational. It carries no role of its own: what is announced is the turn inside it, plus the header’s own text nodes.
  • The avatar is decorative. author already carries the identity, so the face leaves the accessibility tree entirely rather than standing in it as an unnamed image node.
  • The header is text. The author and the timestamp are read as they are written; Glint formats no times, so what a screen reader says is exactly the string you passed.
  • A continuation row says nothing new. grouped: true drops the header along with the avatar, so a run of turns from one sender does not repeat the author on every one of them.
  • Nothing here takes the keyboard. The row owns no control, so it takes no tab stop of its own: what a keyboard reader walks is whatever you slotted into it — an interactive bubble, the footer’s buttons — in the order you wrote them.
  • Hover is not focus. has-hover is a pointer fact. Footer actions driven off it keep their tab stops whether or not they are drawn, so fade them rather than hiding them — otherwise a keyboard user lands on a control that is not on screen.