Bubble
A chat speech bubble — seven token-driven variants (incl. a brand-tinted surface), start/end alignment, interactive content, and a floating reactions chip.
- Status
- Since
0.1.0- Accessibility pattern
- presentational surface
Last updated
Install
Add Bubble from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/bubbleUsage
import { Bubble, BubbleContent } from "@/components/ui/bubble";
<Bubble variant="tinted" align="end">
<BubbleContent>On my way.</BubbleContent>
</Bubble>;Anatomy
A Bubble skins its BubbleContent child via the variant — the surface
colour lives on the content element so padding and the rounded corners stay put.
Bubble— the container (data-slot="bubble",data-variant,data-align). Inside aMessagethe row'salignis inherited.BubbleContent— the rounded surface that carries the text. Built on Base UIuseRender: passrender={<button />}(or ana) for an interactive bubble — it gets a hover surface, the global:focus-visibleoutline, and a focus border tint.BubbleGroup— stacks consecutive bubbles from one sender.BubbleReactions— a floating reactions chip pinned to a bubble corner (side,align).
Examples
Variants
default (the dark "sent" surface), secondary, muted, tinted (a brand
surface), outline, ghost, and destructive.
Alignment
align="start" for received, align="end" for sent. Inside a Message the
row's alignment is inherited automatically.
Grouped
BubbleGroup stacks consecutive bubbles from the same sender.
Conversation with reactions
Sent / received alignment, grouped, with a BubbleReactions chip.
Reaction placement
BubbleReactions pins to a corner via side (top/bottom) and align
(start/end); it also holds multiple emoji or an overflow count.
Interactive (quick replies)
Render BubbleContent as a button (or an a) for tappable quick-reply or
link bubbles — they get a hover surface and the global :focus-visible outline.
Motion
Pass animateIn on a bubble you append after mount — a streamed reply, or one the user just
sent — to fade + rise it in (motion-enter-up). It's off by default: an existing transcript
must not animate every bubble on page load. Message takes the same prop, so set either or both.
Collapsible
Compose a Collapsible inside a bubble for
"show more / less" on long messages.
With a popover
Hang a Popover off a reaction chip — e.g. error
details on a failed message.
With a tooltip
A read-receipt check icon in a reaction chip with a
Tooltip.
Playground
Try all seven surface variants, both alignments, and the opt-in entry animation — each change replays it — then copy the generated JSX.
<Bubble>
<BubbleContent>On my way — be there in five.</BubbleContent>
</Bubble>API Reference
Bubble
| Prop | Type | Default | Description |
|---|---|---|---|
align | "end" | "start" | 'start' | Which side the bubble hugs.
- start: received (default).
- end: sent — self-aligns to the end edge. |
animateIn | boolean | false | Opt-in entry animation (motion-enter-up, a fade + slight rise) for a
bubble that is newly appended to a live thread. **Default off**: an
existing transcript rendered on page load must not animate every bubble.
Enable it only on bubble(s) you append after mount — e.g. a streamed reply
or a message the user just sent (mirrors Message's animateIn; set
either or both). |
variant | BubbleVariant | 'default' | Surface skin.
- default: dark neutral, for sent messages.
- secondary / muted: neutral received surfaces.
- tinted: brand-tinted received surface.
- outline / ghost: quiet surfaces.
- destructive: error / failed message. |
Data attributes and CSS variables on Bubble
| Attribute | Values |
|---|---|
data-align | mirrors a prop or state value |
data-slot | "bubble" |
data-variant | mirrors a prop or state value |
BubbleContent
The render prop turns the surface into an interactive element — pass
render={<button />} (or an a) for a tappable quick-reply or link bubble; it
then gets a hover surface and the global :focus-visible outline.
| Prop | Type | Default | Description |
|---|---|---|---|
render | useRender.RenderProp<Record<string, unknown>> | — | Render the content surface as a different element (e.g. a button for an
interactive bubble, or an a for a link bubble) via Base UI render. |
BubbleReactions
| Prop | Type | Default | Description |
|---|---|---|---|
align | "end" | "start" | 'end' | Horizontal anchor relative to the bubble. |
side | "bottom" | "top" | 'bottom' | Vertical anchor relative to the bubble. |
Data attributes and CSS variables on BubbleReactions
| Attribute | Values |
|---|---|
data-align | mirrors a prop or state value |
data-side | mirrors a prop or state value |
data-slot | "bubble-reactions" |
BubbleGroup
BubbleGroup is a layout wrapper with no own props — it accepts standard
<div> props (className, children, …).
Accessibility
- A non-interactive
BubbleContentis a plain surface. Render it as abutton/aonly when it is genuinely actionable — the interactive surface never strips the outline: it keeps the global:focus-visibleoutline and additionally tints its border with theringtoken ([button,a]:focus-visible:border-ring/(--alpha-tint-border)). - Variant colours are token-driven and contrast-checked in both themes; the
tintedsurface uses the brand family and stays readable light + dark. BubbleReactionsis presentational — give interactive reaction controls their own accessible labels.
| Contract | States tested |
|---|---|
| Behaviour | default, error |
| Accessibility | focus-visible |
| Visual | default, focus, error, dark |
Do / Don't
Message
Layout primitives for a conversation row — avatar anchoring, a content column, header/footer slots, and start/end alignment. Server-safe.
Attachment
A file chip / thumbnail card for chat and message-compose surfaces — media slot, name + meta, uploading/error/complete states, and remove/download actions.