Bubble

The message surface itself, with the variant, the alignment and the reactions that sit on it.

I checked the registry output and removed the stale route.
Thanks, that was the last one.
👍

Installation

uvx django_shadcn@latest add bubble

Note: This pulls in Collapsible, which the Long messages example below builds on, and with it the Alpine.js Collapse plugin. The bubble itself has no JavaScript.

Usage

<c-bubble variant="muted">
    <c-bubble.content>How can I help you today?</c-bubble.content>
</c-bubble>

The variant lives on the root and the padding lives on the content, so the reactions can hang off the edge of the surface without the surface clipping them. That split is why the two tags are always written together.

Variants

Seven, matching the upstream registry. default is the primary colour, for the side of the conversation the reader is on; muted and secondary are the neutral surfaces for the other side.

Default
Secondary
Muted
Tinted
Outline
Ghost, with no surface at all
Destructive
<c-bubble variant="secondary">...</c-bubble>
<c-bubble variant="tinted">...</c-bubble>
<c-bubble variant="ghost">...</c-bubble>

Alignment

align="end" pushes the bubble to the far side. Inside a Message the row already sets the side, and the bubble follows it without being told.

Aligned to the start.
Aligned to the end.
<c-bubble align="end">
    <c-bubble.content>Aligned to the end.</c-bubble.content>
</c-bubble>

Examples

Reactions

Reactions overlap the edge of the surface. side picks the top or the bottom edge, align picks the corner.

Bottom and end, the default.
🔥 +8
Top and start.
👍
<c-bubble variant="muted">
    <c-bubble.content>Bottom and end, the default.</c-bubble.content>
    <c-bubble.reactions>
        <span>🔥</span>
        <span>+8</span>
    </c-bubble.reactions>
</c-bubble>

<c-bubble.reactions side="top" align="start">...</c-bubble.reactions>

A row of emoji says nothing to a screen reader on its own. Name it:

<c-bubble.reactions role="img" aria-label="Reactions: fire, and 8 more">
    <span>🔥</span>
    <span>+8</span>
</c-bubble.reactions>

A bubble that does something

tag renders the content as a button or a link instead of a plain box, so a suggested reply can be clicked and a shared file can be opened. The hover and focus treatment is already in the variant, waiting for an element that can take it.

<c-bubble variant="outline">
    <c-bubble.content tag="button">I forgot my password</c-bubble.content>
</c-bubble>

<c-bubble variant="outline">
    <c-bubble.content tag="a" href="/help/">Read the guide</c-bubble.content>
</c-bubble>

A button gets type="button" unless you pass your own, so a bubble inside a form does not submit it by accident.

Long messages

A long message is a wall. Wrap the bubble in a Collapsible and put the toggle in the content, exactly as the upstream example does — the bubble stays presentational and the open state lives one level up.

The focus ring was being drawn by the primitive and by the style file at the same time, which is why it doubled up on the outline variant.

Moving it to the style file keeps the primitive neutral, so the other themes can pick their own treatment later instead of overriding this one.

<c-collapsible>
    <c-bubble variant="muted">
        <c-bubble.content>
            <p>The first paragraph, always visible.</p>
            <c-collapsible.content>
                <p>The rest, hidden until asked for.</p>
            </c-collapsible.content>
            <c-collapsible.trigger>
                <span x-text="open ? 'Show less' : 'Show more'">Show more</span>
            </c-collapsible.trigger>
        </c-bubble.content>
    </c-bubble>
</c-collapsible>

Grouped

group stacks consecutive bubbles from the same sender at a tighter rhythm than separate messages.

One thought.
Then another, a second later.
<c-bubble.group>
    <c-bubble variant="muted">
        <c-bubble.content>One thought.</c-bubble.content>
    </c-bubble>
    <c-bubble variant="muted">
        <c-bubble.content>Then another.</c-bubble.content>
    </c-bubble>
</c-bubble.group>