Skip to content
GitHub

Bubble

Displays conversational content in a message bubble. Supports variants, alignment, grouping, reactions, 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.
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/components/features/pxl/conversation/bubble";
import { Emoji } from "@/components/ui/pxl/emoji";
export default function BubbleDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-8">
<Bubble align="end">
<BubbleContent>Hey there! what&apos;s up?</BubbleContent>
</Bubble>
<BubbleGroup>
<Bubble variant="muted">
<BubbleContent>Hey! Want to see chat bubbles?</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
I can group messages, switch sides, and keep the whole thread easy
to scan.
</BubbleContent>
<BubbleReactions role="img" aria-label="Reaction: thumbs up">
<Emoji as="span">👍</Emoji>
</BubbleReactions>
</Bubble>
</BubbleGroup>
<Bubble align="end">
<BubbleContent>Sure. Hit me with your best demo.</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
Yes. You are reading a demo that is demoing itself. Very meta. Very
on-brand.
</BubbleContent>
<BubbleReactions
role="img"
aria-label="Reactions: thumbs up, fire, eyes, and 2 more"
>
<Emoji as="span">👍</Emoji>
<Emoji as="span">🔥</Emoji>
<Emoji as="span">👀</Emoji>
<span>+2</span>
</BubbleReactions>
</Bubble>
</div>
);
}
pnpm dlx shadcn@latest add pxl-ui/registry/conversation/bubble

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.

import {
Bubble,
BubbleContent,
BubbleReactions
} from "@/components/features/pxl/conversation/bubble"
<Bubble>
<BubbleContent>
I checked the registry output and removed the stale route.
</BubbleContent>
<BubbleReactions>
<span>👍</span>
</BubbleReactions>
</Bubble>

Use the following composition to build a bubble:

Bubble
├── BubbleContent
└── BubbleReactions

Use BubbleGroup to group consecutive bubbles from the same sender:

BubbleGroup
├── Bubble
│ └── BubbleContent
└── Bubble
└── BubbleContent
  • Seven visual variants, from a strong primary 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 80% of the container width
  • Polymorphic content via render for link and button bubbles
  • Customizable styling through the className prop on every part

Use variant to change the visual treatment of the bubble.

Variant Description
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.

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.

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

align Description
start Align the bubble to the start of the conversation.
end Align 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 component. You can use the role prop on the Message component to automatically align the bubble to the start or end of the conversation.

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

You can turn a bubble into a link or button by using the render prop on BubbleContent.

import { Bubble, BubbleContent } from "@/components/features/pxl/chats/bubble"
export function BubbleLinkDemo() {
return (
<Bubble variant="muted">
<BubbleContent render={<button />}>Click here</BubbleContent>
</Bubble>
)
}

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.

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.

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

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

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

Reactions render as a row of emoji. A screen reader reads each glyph with no context, and counters like +8 are announced as “plus eight”. Group the row as a single image with a descriptive aria-label so it announces once. role="img" also hides the individual emoji from assistive tech, so no aria-hidden is needed.

<BubbleReactions role="img" aria-label="Reactions: thumbs up, fire, and 8 more">
<Emoji as="span">👍</Emoji>
<Emoji as="span">🔥</Emoji>
<Emoji as="span">+8</Emoji>
</BubbleReactions>

When reactions are interactive, render buttons instead and give icon-only buttons an aria-label.

<BubbleReactions>
<Button aria-label="Thumbs up" variant="secondary" size="icon-xs">
<ThumbsUpIcon />
</Button>
</BubbleReactions>

When a bubble is clickable, render it as a real <button> or <a> with the render prop 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. No extra label is needed.

<Bubble variant="muted" align="end">
<BubbleContent render={<button type="button" onClick={onReply} />}>
I forgot my password
</BubbleContent>
</Bubble>

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.

The root bubble wrapper.

Prop Type Default Description
variant "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" "default" The bubble visual treatment.
align "start" | "end" "start" The inline alignment of the bubble.
className string - Additional classes to apply to the root element.

The bubble content wrapper.

Prop Type Default Description
render ReactElement | function - Render the content as a different element such as a link.
className string - Additional classes to apply to the content element.

Displays overlapped reactions for a bubble.

Prop Type Default Description
side "top" | "bottom" "bottom" The side of the bubble to anchor the reactions.
align "start" | "end" "end" The inline alignment of the reactions.
className string - Additional classes to apply to the reaction row.

Groups consecutive bubbles from the same sender.

Prop Type Default Description
className string - Additional classes to apply to the group root.