sdxc

Type to search, or start from one of these:

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.

Morning! Invoice 4821 came through twice this month — once on the 2nd and once on the 14th. Can you take a look?
Found it: the retry fired after the first charge settled.
The duplicate is refunded, back on the card in three to five business days.
Perfect, thank you.
Conversation closed by Ana Souza · 09:41
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/ui
import "@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.

  • Bubble
  • Bubble.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's variant via CSS inheritance.
  • Bubble.ReactionsRenders Bubble.ReactionsProps.children as a role="group" row: a negative block-start margin pins it into the frame's lower padding, aligned via the --ui-bubble-reactions-justify property the frame sets from align.
  • 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.

PropTypeDescription
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.
childrenRemixNodeThe frame's compound parts: Bubble.Content, an optional Bubble.Reactions, or any other content.

Also accepts everything in TagProps<"div">.

Bubble.Content

PropTypeDescription
childrenRemixNodeThe turn's text — plain, or wrapped in a typography layer for rendered markdown.

Also accepts everything in TagProps<"div">.

Bubble.Reactions

PropTypeDescription
"aria-label"stringAccessible label describing the row as a set of reactions.
childrenRemixNodeOne or more already-pressed toggle controls, each scoped to a single reaction.

Also accepts everything in Omit<TagProps<"div">, "aria-label">.

Bubble.Group

PropTypeDescription
childrenRemixNodeTwo or more Bubble frames from the same turn, stacked into one run.

Also accepts everything in TagProps<"div">.

Types

NameValuesWhat 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.