Bubble

Displays conversational content in a message bubble. Supports variants, alignment, and collapsible content.

Hey there! what's up?
Hey! Want to see chat bubbles?
I can group messages, switch sides, and keep the whole thread easy to scan.
Sure. Hit me with your best demo.
Yes. You are reading a demo that is demoing itself. Very meta. Very on-brand.

The Bubble component displays framed conversational content. Use it for chat text, short structured output and quoted replies.

For full-featured chat interfaces, use the Message component. Bubble is intentionally scoped to the bubble surface. Place avatars, names, timestamps, metadata, and message-level actions in Message.

Installation

import { Bubble } from "@o/pipeline/experimental/components/chat/bubble";

Usage

import { Bubble } from "@o/pipeline/experimental/components/chat/bubble";
<Bubble.Root>
  <Bubble.Content>
    I checked the registry output and removed the stale route.
  </Bubble.Content>
</Bubble.Root>

A bubble on its own is content and nothing else. What a message collects and what it offers — reactions, copy, a thread, the time — belong to Message.Footer, which is a row below the bubble rather than an overlay on it.

Composition

Use the following composition to build a bubble:

Bubble.Root
└── Bubble.Content

Consecutive bubbles from one sender are grouped by MessageGroup, not here: what they share is an avatar and a name, which are the message’s and not the bubble’s.

Features

  • Six visual variants, from the reader’s own green bubble to unframed ghost content
  • Start and end alignment for sender and receiver bubbles
  • Reactions that anchor to the bubble edge with configurable side and alignment
  • Bubbles size to their content, up to min(30rem, 100% - 1rem)
  • Customizable styling through the className prop on every part

Variants

Use variant to change the visual treatment of the bubble.

This is the default primary bubble.
This is the secondary variant.
And an elevated one, raised off the surface rather than tinted.
A faint neutral fill without a visible border.
Or a destructive variant with a reaction.

Ghost bubbles work for assistant text, markdown, and other content that should not be framed.

This is perfect for assistant messages that should not have a frame and can take the full width of the container. You can also render code in it.

Ghost bubbles are full width and can take the full width of the container.

VariantDescription
defaultThe reader’s own message, in the brand green.
secondaryEveryone else’s, and anything unattributed: a tint.
subtleFaint neutral fill without a visible border.
elevatedRaised off the surface rather than tinted.
ghostUnframed content for assistant text or rich content.
dangerA destructive bubble for error or failed actions.

Green identifies the reader’s own messages. Other messages use neutral surfaces: a tint for another participant, or a fainter fill when the conversation calls for a quieter treatment. Unattributed messages also stay neutral.

A bubble sizes to its content, up to min(30rem, 100% - 1rem). The ghost variant spans the full row for assistant text and rich content.

Alignment

Use align on Bubble.Root to align the bubble to the start or end of the conversation.

This bubble is aligned to the start. This is the default alignment.
This bubble is aligned to the end. Use this for user messages.
alignDescription
startAlign the bubble to the start of the conversation.
endAlign the bubble to the end of the conversation.

Note: When building chat interfaces, you probably want to use alignment on the Message component itself, not the Bubble.Root component. You can use the align prop on the Message component to automatically align the bubble to the start or end of the conversation.

A bubble is content, never a control, so Bubble.Content has no render prop to turn it into a link or a button. Put a link in the text, place a Button inside or beside the bubble, or use richer UI for choices such as suggested replies.

Show More / Collapsible

Long bubble content can be composed with Collapsible to allow for a show more or show less interaction. Use the Collapsible.Trigger component to trigger the collapsible content.

How can I help you today?
The accessibility review found two focus states that were visually too subtle in dark mode. I checked the dialog, menu, and drawer paths because each one renders focusable control…

Tooltip

Wrap a bubble in a Tooltip to reveal metadata on hover, such as when a message was read.

Did you remove the stale route?
Yes, removed it from the registry.

Popover

Pair a bubble with a Popover to surface more information on demand, such as the full error message for a failed action.

Run the build script.
Failed to run the command.

Accessibility

Bubble.Root renders the presentational message surface. Keep conversation-level semantics on the surrounding container and follow the guidelines below.

Meaning Beyond Color

Bubble variants signal role and tone with color. Pair them with text, alignment, or icons so meaning is not conveyed by color alone. For a destructive bubble, keep the error context in the message text rather than relying on the color treatment.

API Reference

Bubble.Root

The bubble’s row. Full width, so hovering anywhere level with the bubble counts as reaching for it rather than only the words themselves; the visible bubble is Bubble.Content, which sizes to its text and hugs the aligned side. It carries group/bubble, so a hover-revealed control inside one bubble responds to that bubble and not its neighbours.

PropTypeDefaultDescription
variant"default" | "secondary" | "subtle" | "elevated" | "ghost" | "danger""default"The bubble visual treatment.
align"start" | "end""start"The inline alignment of the bubble.
classNamestring-Additional classes to apply to the root element.

Bubble.Content

The visible bubble: the fill, the border and the width cap live here, and it is a positioning context, so a Message.Actions placed inside anchors to the bubble rather than to the row.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the content element.