Bubble
One message’s surface: the rounded chip that carries the text, an image, or a line of prose, aligned to whichever side its author sits on. It knows nothing about who sent it or when — Message owns the row and Chat owns the thread.
Import
import { Bubble } from "@photon-ai/pho-ui/components/bubble";
Anatomy
Root is the bubble’s column and takes the variant and align.
Content is the surface itself; Reactions hangs off a corner. Stack one
author’s consecutive bubbles in a Group.
<Bubble.Root variant="inverse" align="end">
<Bubble.Content>On my way.</Bubble.Content>
<Bubble.Reactions>👍</Bubble.Reactions>
</Bubble.Root>
Variants
Monochrome, like every Pho surface. outlined (the default) is the card
surface with the site hairline — a message received. inverse is the
inverted chip, the site’s message-bubble token — a message sent. subtle
is the quiet surface for a note or a quote; ghost drops the surface for
prose from an assistant and runs full width; error is the send that did
not go through.
Align
start is the other side; end is you. The bubble caps at 80% of the row
so the other side stays open, and its tail corner — a tighter sm radius
— points at the author.
Interactive
Content renders through Base UI’s render, so a bubble can be the
button that opens the message’s record, or a router link, without a
wrapper. Only then does it take the hover wash and the focus ring.
selected lifts the open one with a brand ring; pair it with
aria-pressed so the state is announced.
<Bubble.Content
render={
<button type="button" aria-pressed={open} aria-label="Open message" />
}
selected={open}
onClick={() => select(message.id)}
>
{message.text}
</Bubble.Content>
Reactions
A small pill overlapping a corner — an emoji tapback, a count. It hangs
from the bottom by default, off the corner away from the tail; side
and align move it.
Group
Consecutive bubbles from one author sit in a Group: a 4px stack, tighter
than the 12px between messages, so they read as one turn.
Props
Bubble.Root
- variant —
outlined | inverse | subtle | ghost | error— defaultoutlined - align —
start | end— defaultstart - …plus any
<div>attribute
Bubble.Content
- render — a render element or function (
<button />, a routerLink) - selected —
boolean— brand ring; setsdata-selected - …plus any attribute of the rendered element
Bubble.Reactions: side (top | bottom, default bottom), align
(start | end, default: away from the tail).
BUBBLE_VARIANTS is exported for galleries and tests.
Best practices
- Text keeps its line breaks and wraps; the bubble never scrolls.
- Rings offset against
--pho-bubble-backdrop—Chat.Messagessets it to the thread surface. Set it yourself on any other backdrop. - The bubble is a surface, not a row. Time, status, and the sender go in
Message.Time/Message.Footer/Message.Header, not inside the chip.