Speaker. user → trailing/right (cyan jewel card); agent → leading/left (soft lifted plane); system → centered, muted divider bubble.
children
ReactNode
n/a
The message body. The bubble is a pure container. It does not render a header, markdown, or tool panels (that is AgentMessage).
group
'single' | 'first' | 'middle' | 'last'
'single'
Position in a consecutive same-sender run. The tail stays on the edge bubbles (first/last); interior speaker-side corners square off and spacing tightens.
delivery
'sending' | 'sent' | 'failed'
n/a
Own-message delivery state. Adds a warm receipt (clock · check · alert) under the bubble; failed restyles the bubble to the banner inline-alert grammar (inset status disc + whisper-red border + lifted neutral surface) with title, message and Retry. Never color-alone.
timestamp
string
n/a
Short time string shown in the meta line under the bubble (e.g. "10:23 AM").
selected
boolean
false
Selected / long-pressed: a cyan ring at the selection weight, with no wash (law §6, state reads by outline) and never a glow.
onSelect
() => void
n/a
Tap-to-select handler for selection (a toggle, marked by the cyan ring), distinct from the long-press pop menu (a menu of actions); the two compose. When set, the whole bubble becomes one focusable button (one accessible name).
onRetry
() => void
n/a
Retry handler for a failed bubble; renders a real, focusable Retry button.
Parameter
Type
Default
Description
message *
ChatMessage
n/a
The message model: role drives alignment + surface (USER cyan gradient / ASSISTANT cardAlt plane / SYSTEM centered divider) and carries the body blocks.
showHeader
Boolean
true
Grouping control. The list passes !isContinuation so a consecutive same-sender bubble collapses its header and reads as part of the run.
accentColor
Color
Color.Unspecified
Optional override for the user bubble fill. Unspecified → the default cyan userCard2 → userCard gradient.
bubbleMaxWidth
Dp
320.dp
Upper bound on bubble width (widthIn(max=)). Phone cap by default; the list passes a wider value on tablets.
Not yet implemented. Nockerl Voice is a dictation/transcription surface with no
chat thread, so no bubble primitive ships on macOS. The signature below is the
intended SwiftUI API (a ViewBuilder content closure), derived from the laws
the shipped Compose bubble container.
Parameter
Type
Default
Description
role *
BubbleRole
n/a
.user → trailing/right; .agent → leading/left; .system → centered. Drives alignment, surface, and the tail corner.
group
GroupPosition
.single
Position in a same-sender run, which keeps the tail on the edge bubbles and squares the interior seams.
delivery
DeliveryState?
nil
Own-message delivery state (.sending / .sent / .failed), shown as a warm receipt under the bubble; never color-alone.
timestamp
String?
nil
Short time string in the meta line under the bubble.
content *
() -> Content
n/a
A @ViewBuilder body. The bubble is a container; the rich turn is composed on top.
Cross-platform drift (tracked for reconciliation)
Primitive vs. composition: this is the bubble container (shape · tail ·
role alignment · grouping · delivery/selection). The full assistant turn
(identity header, markdown, tool-call panels, streaming) is
Agent message, composed on this bubble. Tool
panels and markdown are documented there, not here; note the bottom-of-chat
streaming indicator there is design-only (documented, not shipped in the
products).
Swift: no chat surface ships yet; Voice is transcription-only. The Swift
API above is intended, not implemented.
Bubble radius: Compose MessageBubble hardcodes the shape at 18.dp /
6.dp, while bubbleShape (and the web demo) default to the token 20 /
6 (radius.bubble / radius.bubble-tail). The bubble call-site should
consume the token.
Grouping seams(resolved): the web grouping
model wins: interior speaker-side corners of a same-sender run square off, one tail
per run. Compose (today collapses only the header, keeping every bubble’s full tail)
conforms.
Delivery receipts(resolved): the sending / sent / failed lane + the
in-thread failed bubble built on the Banner inline-alert grammar (inset status
disc + whisper-red border + lifted neutral surface + Retry) is the ratified model. Android
moves stream errors off the floating status pill and per-bubble.