Toggle Sidebar B
AppearanceLight & dark mode D

Bubble

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

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

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.

Dark previewReplay preview
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.

Variants

Use variant to change the visual treatment of the bubble.

A bubble sizes to its content, up to 80% of the container width. The ghost variant removes the max-width so assistant text and rich content can span the full row.

  • default: a strong primary bubble, usually for the current user.
  • secondary: the standard neutral bubble for conversation content.
  • muted: a lower-emphasis bubble for quiet supporting content.
  • tinted: a subtle primary-tinted bubble.
  • outline: a bordered bubble for secondary or rich content.
  • ghost: unframed content for assistant text or rich content.
  • destructive: a destructive bubble for error or failed actions.
Dark previewReplay preview
This is the default primary bubble.
This is the secondary variant.
This one is muted. It uses a lower emphasis color for the chat bubble.
This one is tinted. The tint is a softer color derived from the primary color.
We can also use an outlined variant.
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.

Alignment

Use align on Bubble to align the bubble to the start or end of the conversation. start puts it at the start, end puts it at the end.

Note: when building chat interfaces, you probably want to set align on the Message component itself. Bubbles inside MessageContent automatically follow the message alignment.

Dark previewReplay preview
This bubble is aligned to the start. This is the default alignment.
This bubble is aligned to the end. Use this for user messages.

Bubble Group

Use BubbleGroup to group consecutive bubbles from the same sender. Note the align prop should be set on the Bubble component itself, not the BubbleGroup component.

BubbleGroup
โ”œโ”€โ”€ Bubble
โ”‚   โ””โ”€โ”€ BubbleContent
โ””โ”€โ”€ Bubble
    โ””โ”€โ”€ BubbleContent
Dark previewReplay preview
Can you tell me what's the issue?
You tell me!
It worked yesterday. You broke it!
Find the bug and fix it.
Want me to diff yesterday's you against today's you? It's a bit embarrassing.

Reactions

Use BubbleReactions for bubble reactions. You can use it to display reactions or quick action buttons. Use side and align to position the row, side="top" anchors it to the upper edge. Reactions overlap the bubble edge, so leave vertical space between rows; the examples below use a larger gap for this reason.

Dark previewReplay preview
I don't need tests, I know my code works.
Bold. Fine I'll add some tests. I'll let you know when they're done.
Tests passed on the first try. All 142 of them. Looking good!
Are you sure I can run this command?

Show More / Collapsible

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

Dark previewReplay preview
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.

Dark previewReplay preview
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.

Dark previewReplay preview
Run the build script.
Failed to run the command.

Styling

Every part carries a data-slot attribute and its variant state, so a style sheet can paint the whole surface without touching the components. Bubble sets data-slot="bubble" plus data-variant and data-align; BubbleContent sets data-slot="bubble-content"; BubbleReactions sets data-slot="bubble-reactions" plus data-align and data-side; BubbleGroup sets data-slot="bubble-group".

The index also exports two cva helpers, bubbleVariants and bubbleReactionsVariants, if you need the same class list outside the components.

Two classes read state from a parent, so keep them in mind when composing: .cn-bubble reads group-data-[align=end]/message from a surrounding Message, and .cn-bubble-content reads group-data-[align=end]/bubble from its own Bubble. Color is applied to the content slots rather than the bubble root, which is why a single Bubble can hold several BubbleContent blocks and paint each one as its own pill.

Anatomy

Import all parts and piece them together.

vue
<script setup>
import {
  Bubble,
  BubbleContent,
  BubbleGroup,
  BubbleReactions,
} from "@/components/ui/bubble";
</script>

<template>
  <BubbleGroup>
    <Bubble>
      <BubbleContent />
      <BubbleReactions />
    </Bubble>
  </BubbleGroup>
</template>

API Reference

Bubble

PropTypeDefaultDescription
variant"default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive""default"The bubble visual treatment. Also written to data-variant.
align"start" | "end""start"The inline alignment of the bubble. Also written to data-align.
classstringโ€”Extra classes, merged with cn().
SlotDescription
defaultBubbleContent blocks and an optional BubbleReactions row.

BubbleContent

PropTypeDefaultDescription
asstring | Component"div"Element or component to render. Forwarded to reka-ui Primitive.
asChildbooleanfalseRender the default slot as the root and merge props onto it, for a link or button bubble.
classstringโ€”Extra classes, merged with cn().
SlotDescription
defaultMessage text or rich content.

BubbleReactions

PropTypeDefaultDescription
side"top" | "bottom""bottom"Bubble edge the row anchors to. Also written to data-side.
align"start" | "end""end"Inline alignment along that edge. Also written to data-align.
classstringโ€”Extra classes, merged with cn().
SlotDescription
defaultEmoji glyphs or buttons. The pill background, ring, and spacing come from the row itself.

BubbleGroup

PropTypeDefaultDescription
classstringโ€”Extra classes, merged with cn().
SlotDescription
defaultConsecutive Bubble elements from one sender.

Accessibility

Keyboard shortcuts and ARIA behavior.

Shortcut Description
TabMoves focus to the next interactive bubble, reaction button, or trigger.
EnterActivates the focused button or link inside a bubble.
SpaceActivates the focused button inside a bubble.
  • Bubble renders the presentational message surface. Keep conversation-level semantics on the surrounding container.
  • Reactions render as a row of emoji, and a screen reader reads each glyph with no context, so a counter like "+8" is announced as "plus eight". Group the row as a single image with role="img" and a descriptive aria-label so it announces once. role="img" also hides the individual glyphs, so no aria-hidden is needed.
  • When reactions are interactive, render buttons instead and give icon-only buttons an aria-label.
  • When a bubble is clickable, pass a real button or anchor through BubbleContent with as-child so it is focusable and exposes the correct role. BubbleContent ships a visible focus ring for interactive elements, and the accessible name comes from the bubble text.
  • Variants signal role and tone with color. Pair them with text, alignment, or icons so meaning is not carried by color alone; for a destructive bubble, keep the error context in the message text.