Bubble
The framed message surface inside a conversational row's content slot, sized to its own content up to a share of the row's width.
View code
<div mix={[vstack({ gap: 3, align: "stretch" }), is("100%"), maxIs("30rem")]}>
<Bubble align="start" variant="muted">
<Bubble.Content>
Morning! Invoice 4821 came through twice this month — once on the 2nd and once on
the 14th. Can you take a look?
</Bubble.Content>
</Bubble>
<Bubble.Group>
<Bubble align="end">
<Bubble.Content>Found it: the retry fired after the first charge settled.</Bubble.Content>
</Bubble>
<Bubble align="end">
<Bubble.Content>
The duplicate is refunded, back on the card in three to five business days.
</Bubble.Content>
</Bubble>
</Bubble.Group>
<Bubble align="start" variant="muted">
<Bubble.Content>Perfect, thank you.</Bubble.Content>
<Bubble.Reactions aria-label="Reactions to this message">
<ToggleButton
size="sm"
variant="outline"
aria-pressed="true"
aria-label="Thumbs up, 3 people"
mix={[pressToggle()]}
>
👍 3
</ToggleButton>
<ToggleButton
size="sm"
variant="outline"
aria-pressed="false"
aria-label="Party, 1 person"
mix={[pressToggle()]}
>
🎉 1
</ToggleButton>
</Bubble.Reactions>
</Bubble>
<Bubble align="start" variant="ghost">
<Bubble.Content mix={[text("xs"), fg("neutral.muted")]}>
Conversation closed by Ana Souza · 09:41
</Bubble.Content>
</Bubble>
</div>Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/uiimport "@sdxc/ui/theme.css";Usage
import { Bubble } from "@sdxc/ui";Renders the frame, pushed to the edge align names via auto margins so it
aligns correctly regardless of its row's layout. Its nearest corner falls
back to a literal 0.125rem, since roundedCorner() has no "xs" step.
Composition
The parts the component publishes, each a static property of the host.
BubbleBubble.ContentRenders Bubble.ContentProps.children as the frame's padded text slot, breaking long unbroken runs at the inline box edge and taking its text color from the ancestor Bubble'svariantvia CSS inheritance.Bubble.ReactionsRenders Bubble.ReactionsProps.children as arole="group"row: a negative block-start margin pins it into the frame's lower padding, aligned via the--ui-bubble-reactions-justifyproperty the frame sets fromalign.Bubble.GroupRenders Bubble.GroupProps.children as a tightly spaced column of consecutive Bubble frames, softening only the corners touching a neighbor to a smaller radius, so the run reads as one continuous shape.
Examples
<Bubble variant="muted" align="start">
<Bubble.Content>{turn.text}</Bubble.Content>
</Bubble><Bubble variant="ghost" align="start">
<Bubble.Content>
<Typeset preset="chat">{renderedMarkdownHtml}</Typeset>
</Bubble.Content>
</Bubble>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Description |
|---|---|---|
variant? | "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" | Visual weight. Defaults to DEFAULT_VARIANT. |
align? | "start" | "end" | Edge of the row the frame hugs. Defaults to DEFAULT_ALIGN. |
children | RemixNode | The frame's compound parts: Bubble.Content, an optional Bubble.Reactions, or any other content. |
Also accepts everything in TagProps<"div">.
Bubble.Content
| Prop | Type | Description |
|---|---|---|
children | RemixNode | The turn's text — plain, or wrapped in a typography layer for rendered markdown. |
Also accepts everything in TagProps<"div">.
Bubble.Reactions
| Prop | Type | Description |
|---|---|---|
"aria-label" | string | Accessible label describing the row as a set of reactions. |
children | RemixNode | One or more already-pressed toggle controls, each scoped to a single reaction. |
Also accepts everything in Omit<TagProps<"div">, "aria-label">.
Bubble.Group
| Prop | Type | Description |
|---|---|---|
children | RemixNode | Two or more Bubble frames from the same turn, stacked into one run. |
Also accepts everything in TagProps<"div">.
Types
| Name | Values | What it decides |
|---|---|---|
Variant | "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" | Visual weight the frame renders with: solid "default"/"secondary" fills, soft "muted"/"tinted" tints, a bordered "outline", an unframed "ghost", and a solid danger fill for "destructive". |
Align | "start" | "end" | Edge of the row the frame hugs: "start" stays flush with the row's leading edge (flipping under dir="rtl"), "end" stays flush with the trailing edge, typically paired with the reader's own turns vs everyone else's. |