Skip to main content

Bubble

The message surface for chat interfaces, with variants and reactions.

Hey, did you ship the new docs page?
The nav check is failing on my branch.
Just pushed the fix, pull and rerun it.
2
1import { Bubble } from '@/components/ui/bubble';
2
3export function Default() {
4 return (
5 <div className="flex w-full max-w-sm flex-col gap-3">
6 <Bubble.Group align="start">
7 <Bubble variant="secondary">
8 <Bubble.Content>Hey, did you ship the new docs page?</Bubble.Content>
9 </Bubble>
10 <Bubble variant="secondary">
11 <Bubble.Content>The nav check is failing on my branch.</Bubble.Content>
12 </Bubble>
13 </Bubble.Group>
14 <Bubble.Group align="end">
15 <Bubble align="end">
16 <Bubble.Content>Just pushed the fix, pull and rerun it.</Bubble.Content>
17 <Bubble.Reactions side="bottom" align="start">
18 <span aria-hidden="true">👍</span>
19 <span className="text-muted-foreground px-0.5">2</span>
20 </Bubble.Reactions>
21 </Bubble>
22 </Bubble.Group>
23 </div>
24 );
25}

Installation

pnpm dlx nachui add bubble

Anatomy

1import { Bubble } from '@/components/ui/bubble';
1<Bubble variant="secondary" align="start">
2 <Bubble.Content>Hey, how is the release going?</Bubble.Content>
3 <Bubble.Reactions>
4 <span>👍</span>
5 </Bubble.Reactions>
6</Bubble>
Bubble is deliberately scoped to the surface itself. Sender name, timestamps, avatars and actions belong to Message, which composes bubbles into a full conversation row.

Composition

Use the following composition to build a Bubble:
Bubble.Group
└── Bubble
├── Bubble.Content
└── Bubble.Reactions

Variants

Seven variants cover both sides of a conversation and the states around it: default for the current user, secondary and muted for the other side, tinted for a subtle primary wash, outline and ghost for quiet content, and destructive for errors.
Default, for the current user.
Secondary, for the other side.
Muted, for supporting content.
Tinted, a subtle primary wash.
Outline, framed but quiet.
Ghost, no frame at all.
Destructive, something went wrong.
1import { Bubble } from '@/components/ui/bubble';
2
3const VARIANTS = [
4 { variant: 'default', text: 'Default, for the current user.' },
5 { variant: 'secondary', text: 'Secondary, for the other side.' },
6 { variant: 'muted', text: 'Muted, for supporting content.' },
7 { variant: 'tinted', text: 'Tinted, a subtle primary wash.' },
8 { variant: 'outline', text: 'Outline, framed but quiet.' },
9 { variant: 'ghost', text: 'Ghost, no frame at all.' },
10 { variant: 'destructive', text: 'Destructive, something went wrong.' },
11] as const;
12
13export function Variants() {
14 return (
15 <div className="flex w-full max-w-sm flex-col gap-2">
16 {VARIANTS.map(({ variant, text }) => (
17 <Bubble key={variant} variant={variant}>
18 <Bubble.Content>{text}</Bubble.Content>
19 </Bubble>
20 ))}
21 </div>
22 );
23}

API Reference

Bubble

PropTypeDefaultDescription
variant'default' | 'secondary' | 'muted' | 'tinted' | 'outline' | 'ghost' | 'destructive''default'Visual treatment of the surface
align'start' | 'end''start'Which side the bubble hugs
classNamestring-Additional CSS classes

Bubble.Content

PropTypeDefaultDescription
asChildbooleanfalseMerge styles into the child element, e.g. a link
classNamestring-Additional CSS classes

Bubble.Reactions

PropTypeDefaultDescription
side'top' | 'bottom''bottom'Edge the pill overlaps
align'start' | 'end''end'Horizontal anchor along that edge
classNamestring-Additional CSS classes

Bubble.Group

PropTypeDefaultDescription
align'start' | 'end''start'Alignment of the stacked bubbles
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I