Data Display · astro-ui · Bubble

Bubble

Chat message bubble — 7 variants, start/end align, reactions, groups.

Bubble composes a conversational message from three parts: Bubble sets alignment (start/end) and variant context, BubbleContent is the visible rounded box, and BubbleReactions anchors a reaction pill to an edge. BubbleGroup stacks consecutive same-sender turns with tight spacing. Ghost renders bare content for headings, lists, and code.

Features

  • 7 variants: primary, secondary, muted, tinted, outline, ghost, destructive
  • start/end alignment for two-sided threads
  • BubbleGroup collapses consecutive same-sender turns
  • Edge-anchored reactions pill (top/bottom, start/end)
  • Interactive content: a button or link as the content gets a themed hover
  • Zero JS — wire reactions and actions app-side

Usage

---
import { Bubble, BubbleContent, BubbleReactions } from "astro-ui";
---

<Bubble align="end" variant="primary">
  <BubbleContent>Looks great — ship it.</BubbleContent>
  <BubbleReactions side="bottom" align="end" ariaLabel="Reactions">
    <span aria-hidden="true">🎉</span>
  </BubbleReactions>
</Bubble>

Demos

Two-sided billing thread

Support · 09:12

Hi! I see a double charge on your last invoice.

You · 09:14

Yes — my card was charged twice for the renewal.
Refunding the duplicate now. It lands in 3–5 business days.
Confirmed — thank you!
<div class="docs-bubble-thread">
  <p class="docs-bubble-meta">Support · 09:12</p>
  <Bubble align="start" variant="outline">
    <BubbleContent>Hi! I see a double charge on your last invoice.</BubbleContent>
  </Bubble>
  <p class="docs-bubble-meta docs-bubble-meta--end">You · 09:14</p>
  <Bubble align="end" variant="primary">
    <BubbleContent>Yes — my card was charged twice for the renewal.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="muted">
    <BubbleContent>Refunding the duplicate now. It lands in 3–5 business days.</BubbleContent>
  </Bubble>
  <Bubble align="end" variant="tinted">
    <BubbleContent>Confirmed — thank you!</BubbleContent>
  </Bubble>
</div>

Seven variants in one deploy thread

Deploy the preview build to production.
On it — building #412.
Last production deploy was v2.4.1, two days ago.
Build passed in 1m 32s.
3 files changed, 128 additions, 12 deletions.
Coverage dropped below 80% — consider adding tests.
Run #412 · runner eu-west-1 · log retained 30 days
<div class="docs-bubble-thread">
  <Bubble align="end" variant="primary">
    <BubbleContent>Deploy the preview build to production.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="secondary">
    <BubbleContent>On it — building #412.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="muted">
    <BubbleContent>Last production deploy was v2.4.1, two days ago.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="tinted">
    <BubbleContent>Build passed in 1m 32s.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="outline">
    <BubbleContent>3 files changed, 128 additions, 12 deletions.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="destructive">
    <BubbleContent>Coverage dropped below 80% — consider adding tests.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="ghost">
    <BubbleContent>Run #412 · runner eu-west-1 · log retained 30 days</BubbleContent>
  </Bubble>
</div>

Consecutive turns in a BubbleGroup

Ada · just now

Pushed the fix to staging.
Can you re-run the smoke tests?
Then we're clear for the release.

Grace · 1 min ago

Running them now.
<div class="docs-bubble-thread">
  <p class="docs-bubble-meta">Ada · just now</p>
  <BubbleGroup ariaLabel="Three messages from Ada">
    <Bubble align="end" variant="primary">
      <BubbleContent>Pushed the fix to staging.</BubbleContent>
    </Bubble>
    <Bubble align="end" variant="primary">
      <BubbleContent>Can you re-run the smoke tests?</BubbleContent>
    </Bubble>
    <Bubble align="end" variant="primary">
      <BubbleContent>Then we're clear for the release.</BubbleContent>
    </Bubble>
  </BubbleGroup>
  <p class="docs-bubble-meta">Grace · 1 min ago</p>
  <Bubble align="start" variant="muted">
    <BubbleContent>Running them now.</BubbleContent>
  </Bubble>
</div>

Reaction rows

The release notes are ready for review.
Great — leaving a celebration on it.
Deployment window opens at 14:00 UTC.
<div class="docs-bubble-thread">
  <Bubble align="start" variant="outline">
    <BubbleContent>The release notes are ready for review.</BubbleContent>
    <BubbleReactions side="bottom" align="end" ariaLabel="Reactions">
      <span aria-hidden="true">👍 4</span>
      <span aria-hidden="true">🚀 2</span>
    </BubbleReactions>
  </Bubble>
  <Bubble align="end" variant="primary">
    <BubbleContent>Great — leaving a celebration on it.</BubbleContent>
    <BubbleReactions side="bottom" align="start" ariaLabel="Reactions">
      <button type="button" aria-pressed="true" aria-label="Celebrate, 3 reactions">
        <span aria-hidden="true">🎉</span> 3
      </button>
    </BubbleReactions>
  </Bubble>
  <Bubble align="start" variant="muted">
    <BubbleContent>Deployment window opens at 14:00 UTC.</BubbleContent>
    <BubbleReactions side="top" align="end" ariaLabel="Reactions">
      <span aria-hidden="true">⏰ 1</span>
    </BubbleReactions>
  </Bubble>
</div>

Ghost bubble with rich content

What changed in v2.5

  • Bubble components for conversational UIs
  • Logical-property separators in Breadcrumb
  • 40% faster docs generation

Upgrade with npm install astro-ui@latest.

<div class="docs-bubble-thread">
  <Bubble align="start" variant="ghost">
    <BubbleContent>
      <h4>What changed in v2.5</h4>
      <ul>
        <li><strong>Bubble</strong> components for conversational UIs</li>
        <li>Logical-property separators in <code>Breadcrumb</code></li>
        <li><em>40% faster</em> docs generation</li>
      </ul>
      <p>Upgrade with <code>npm install astro-ui@latest</code>.</p>
    </BubbleContent>
  </Bubble>
</div>

Long answer behind a Collapsible

Explain how the reactions pill positions itself.

The pill is absolutely positioned inside the bubble and pushed out with a translate of 75% of its own height, so it straddles the edge without occupying flow space.

<div class="docs-bubble-thread">
  <Bubble align="end" variant="primary">
    <BubbleContent>Explain how the reactions pill positions itself.</BubbleContent>
  </Bubble>
  <Bubble align="start" variant="muted">
    <BubbleContent>
      <p>
        The pill is absolutely positioned inside the bubble and pushed
        out with a translate of 75% of its own height, so it straddles
        the edge without occupying flow space.
      </p>
      <Collapsible id="dm-bubble-more">
        <span slot="trigger">Show the details</span>
        <p>
          A 3px ring in the page background separates the pill from the
          bubble and any media behind it. side picks the edge (top or
          bottom) and align picks the horizontal anchor (start or end) —
          both logical, so they mirror in RTL layouts.
        </p>
      </Collapsible>
    </BubbleContent>
  </Bubble>
</div>

Tinted bubbles as buttons + action row

Two plans fit your usage. Pick one:
<div class="docs-bubble-thread">
  <Bubble align="start" variant="muted">
    <BubbleContent>Two plans fit your usage. Pick one:</BubbleContent>
  </Bubble>
  <Bubble align="end" variant="tinted">
    <button type="button" class="aui-bubble__content">Pro — $19/mo, priority builds</button>
  </Bubble>
  <Bubble align="end" variant="tinted">
    <button type="button" class="aui-bubble__content">Team — $49/mo, shared themes</button>
  </Bubble>
  <Bubble align="start" variant="outline">
    <a class="aui-bubble__content" href="#">Compare all plans</a>
  </Bubble>
  <div class="docs-bubble-actions">
    <Button variant="ghost" size="sm">Regenerate</Button>
    <Button variant="outline" size="sm">Copy summary</Button>
  </div>
</div>

API

Props

PropTypeDefaultDescription
align'"start" | "end"''"start"'Side of the thread the message sits on.
variant'"primary" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive"''"primary"'Color scheme of the content box.
classstring—Merged via cn(); Tailwind classes welcome.
idstring—Element id.

Slots

SlotDescription
defaultBubbleContent box and optional BubbleReactions pill.

Accessibility

Implemented accessibility features — semantics, ARIA wiring, and keyboard behavior that ship with the component.

  • Reactions row is a labeled group; pressable reactions are real buttons with aria-pressed
  • BubbleGroup exposes role=group with an accessible name per sender run
  • Variant color pairs keep 4.5:1 contrast on their content

FAQ

How do I make a pressable reaction?

Put a real <button> inside BubbleReactions with aria-pressed; the pill styles it. Toggling state (and the count) is app-side — the component ships zero JS.

How do interactive bubbles work?

Author <button class="aui-bubble__content"> or <a class="aui-bubble__content"> directly instead of BubbleContent — the Bubble root gives interactive content a themed hover and focus ring.

When do I use BubbleGroup?

When one sender sends several turns in a row: group them so spacing tightens and the run reads as a single message block. Give each group an ariaLabel naming the sender.

Changelog

Version history mentioning Bubble.

v0.1.0 — 2026-11-11
added
  • Data display (10): Badge, Avatar, Attachment, Table, Progress, Bubble, Chart, Empty, Tag, Timeline.