# Bubble

Show the text of a chat message on a surface that fits its sender and purpose.

Two messages from two people

[Complete TSX file](/examples/bubble/default.tsx)

```tsx
"use client";

import { Bubble, BubbleContent } from "@/siteplane/components/bubble";

export default function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-8">
      <Bubble>
        <BubbleContent>
          Can you move the renewal review to Thursday?
        </BubbleContent>
      </Bubble>
      <Bubble align="end" variant="default">
        <BubbleContent>Thursday works. I will update the invite.</BubbleContent>
      </Bubble>
    </div>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/bubble
```

With npm: `npx shadcn@4.18.0 add @siteplane/bubble`. [Registry JSON](/registry/bubble.json) lists source files and dependencies.

## Usage

```tsx
import { Bubble, BubbleContent } from "@/siteplane/components/bubble";

<Bubble>
  <BubbleContent>Can you move the renewal review to Thursday?</BubbleContent>
</Bubble>;
```

## Composition

```text
BubbleGroup (optional, for consecutive bubbles)
└── Bubble
    └── BubbleContent
```

`Bubble` places the surface and `BubbleContent` draws it. A bubble is as wide as its text, up to 80 percent of the width it sits in, and a ghost bubble takes the full width. Long words and URLs wrap inside the bubble instead of widening it. In a conversation, put the bubble in a [Message](/docs/components/message/), which holds the avatar, the sender's name, the time and the actions of the turn.

## Variants

Choose the variant by who wrote the message and what it holds.

- `muted`, the default, is the quiet neutral fill, for the person's own turns in an AI chat and for the messages of the others in a conversation between people.
- `default` is the accent fill with the accent's text color, for the person's own messages in a conversation between people.
- `tinted` is a tint of the accent with the primary text color, a quieter choice than `default` for the person's own messages.
- `outline` is the bordered message surface of the Card's `message` variant, with a small corner on the sender's side, for messages that need an edge of their own, such as on a filled surface.
- `ghost` has no surface and takes the full width, for the assistant's text and for rich content such as lists, tables and code.
- `destructive` is the error tint with the error text color, for a message or a reply that failed. Say what failed in the text, since the tint alone does not.

The six variants

[Complete TSX file](/examples/bubble/variants.tsx)

```tsx
"use client";

import { Bubble, BubbleContent } from "@/siteplane/components/bubble";

export default function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Bubble align="end" variant="default">
        <BubbleContent>Can you send me last month's invoice?</BubbleContent>
      </Bubble>
      <Bubble variant="muted">
        <BubbleContent>
          Sure. It is attached to this conversation.
        </BubbleContent>
      </Bubble>
      <Bubble align="end" variant="tinted">
        <BubbleContent>
          Thanks. Please add the purchase order number.
        </BubbleContent>
      </Bubble>
      <Bubble variant="outline">
        <BubbleContent>
          Added. The number is in the footer of the PDF now.
        </BubbleContent>
      </Bubble>
      <Bubble variant="ghost">
        <BubbleContent>
          The September invoice is $1,240 for 31 seats on the Team plan. It was
          paid on October 2 with the card on file.
        </BubbleContent>
      </Bubble>
      <Bubble variant="destructive">
        <BubbleContent>
          The invoice could not be generated. Try again in a few minutes.
        </BubbleContent>
      </Bubble>
    </div>
  );
}
```

## Alignment

`align="end"` places a bubble at the end of its row, for the person who is writing, and `start` is the default. Inside a [Message](/docs/components/message/#end-aligned-turns) with `align="end"`, give the bubble the same `align`. The small corner of an outline bubble moves to the sender's side with it.

## Groups

`BubbleGroup` stacks consecutive bubbles from one sender 8 px apart, such as a message sent in parts. Give each bubble its own `align`; the group does not set it. For consecutive turns that each have their own time or actions, use a [Message Group](/docs/components/message/#groups) instead.

Consecutive bubbles from two people

[Complete TSX file](/examples/bubble/group.tsx)

```tsx
"use client";

import {
  Bubble,
  BubbleContent,
  BubbleGroup,
} from "@/siteplane/components/bubble";

export default function Example() {
  return (
    <div className="flex w-full max-w-md flex-col gap-8">
      <BubbleGroup>
        <Bubble>
          <BubbleContent>Quick update on onboarding.</BubbleContent>
        </Bubble>
        <Bubble>
          <BubbleContent>
            Fourteen new accounts finished setup last week, and none needed a
            manual fix.
          </BubbleContent>
        </Bubble>
      </BubbleGroup>
      <BubbleGroup>
        <Bubble align="end" variant="default">
          <BubbleContent>Nice.</BubbleContent>
        </Bubble>
        <Bubble align="end" variant="default">
          <BubbleContent>Which ones are still open?</BubbleContent>
        </Bubble>
      </BubbleGroup>
    </div>
  );
}
```

## AI SDK

The `text` parts of a `UIMessage` go into bubbles. The examples on this page show fixed messages; the code below takes them from `useChat` instead.

### Text parts

The person’s text goes into a muted bubble at the end, and the assistant’s into a ghost bubble. A text part has `state: "streaming"` while it grows, and the bubble grows with it, without animation.

```tsx
<MessageContent>
  {message.parts.map((part, index) =>
    part.type === "text" ? (
      <Bubble
        align={message.role === "user" ? "end" : "start"}
        key={index}
        variant={message.role === "user" ? "muted" : "ghost"}
      >
        <BubbleContent>{part.text}</BubbleContent>
      </Bubble>
    ) : null,
  )}
</MessageContent>
```

An assistant’s text is Markdown. Render it inside the ghost bubble with your Markdown renderer and the element map of [Typeset](/docs/components/typeset/#ai-sdk), which sets it in the kit’s type.

### Failed requests

When a request fails, `status` is `"error"`. Show a destructive bubble with a short message of your own and Try again, as in the [error turn](/docs/components/message/#error-turn) of the Message page.

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| Bubble.variant | default \| muted \| tinted \| outline \| ghost \| destructive | muted | The surface of the message. ghost has no surface and takes the full width; the others are as wide as their text, up to 80 percent. |
| Bubble.align | "start" \| "end" | "start" | The side of the row the bubble sits on. Inside a Message, give it the Message's align. |
| BubbleContent | div props | — | Draws the surface: 8 px above and below the text, 16 px beside it, a 16 px radius and 14 px text on a 21 px line. Long words and URLs wrap. |
| BubbleGroup | div props | — | Consecutive bubbles from one sender, 8 px apart. Each bubble keeps its own align. |
| --sp-bubble-padding-block / --sp-bubble-padding-inline | length | 8 px / 16 px | The space above and below and beside the text. Message reads the inline padding, so its header and footer start where the text starts. |
| --sp-bubble-radius | length | 16 px | The radius of the surface, the radius of a dialog. It follows the kit's radius setting. |
Every part renders a div and accepts its props, a ref and `className`. Set the
custom properties on a bubble or on any element around it. A bubble is not
interactive: it takes no `render` prop and has no hover or focus state, so a
link or a Button goes inside it or into the footer of its Message. The types
`BubbleVariant` and `BubbleAlign` are exported.

[Read the installed source](/source/bubble).

## Usage notes

A bubble is a surface without conversation semantics; the thread around it, such as the log of a [Message Scroller](/docs/components/message-scroller/#announcements), carries them. A variant's color never carries the meaning alone. Pair it with words, as a destructive bubble says what failed.
