Bubble
The message surface itself, with the variant, the alignment and the reactions that sit on it.
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.
<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.
<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.
<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.
<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.
<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>