bx

Bubble

The chat bubble — 7 variants (default, muted, tinted, outline, ghost, destructive, secondary) with optional reactions.

Hey there! What's up?
Hey! Want to see chat bubbles?
I can group messages, switch sides, and keep the whole thread easy to scan.
👍
tsx
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
  <Bubble align="end">
    <BubbleContent>Hey there! What's up?</BubbleContent>
  </Bubble>
  <BubbleGroup>
    <Bubble variant="muted">
      <BubbleContent>Hey! Want to see chat bubbles?</BubbleContent>
    </Bubble>
    <Bubble variant="muted">
      <BubbleContent>
        I can group messages, switch sides, and keep the whole thread easy to scan.
      </BubbleContent>
      <BubbleReactions aria-label="Reaction: thumbs up">
        <span>👍</span>
      </BubbleReactions>
    </Bubble>
  </BubbleGroup>
</div>

Bubble est le rectangle coloré qui contient un message dans un chat. C'est un wrapper de positionnement (relative + max-width) qui applique aussi le fond et le padding selon un variant.

Deux enfants attendus :

  • BubbleContent — le vrai texte / rich content du message. En pratique un simple <span> — le style (padding, rounded, bg) est sur le parent Bubble. Ce sub-composant existe pour la parité API avec shadcn et pour que BubbleReactions puisse cibler [data-slot=bubble-content] en CSS custom.
  • BubbleReactions — badge en absolute (bas-droite par défaut) qui contient des emojis ou boutons de réaction. Sort de la bulle via translate-y-3/4, cadré d'un ring-white. Optionnel.

7 variants définissent l'aspect :

  • defaultbg-primary text-primary-foreground. Pour les messages envoyés par l'utilisateur courant (accent fort).
  • secondarybg-secondary text-secondary-foreground. Alternative moins saturée.
  • mutedbg-muted text-foreground. Pour les messages reçus (interlocuteurs). Le plus courant en chat multi-party.
  • tintedbg-primary/10. Teinté accent, plus doux que default. Pour souligner sans crier.
  • outlineborder bg-background. Rectangle bordé sans fond, look "email draft".
  • ghost — sans padding ni fond. Pour les longues notes assistant qui s'intègrent au flow textuel sans frame.
  • destructivebg-destructive/10 text-destructive. Pour les erreurs / warnings inline.

Pour empiler plusieurs bulles du même auteur (chaînes de messages courts, réactions séquentielles), envelopper dans BubbleGroupflex flex-col gap-2 qui garde le rendu compact.

Combiné à Message pour l'alignement gauche/droite + avatar, ça forme la primitive de tout thread conversationnel.

Installation

Terminal
$ bext ui add bubble

Usage

tsx
import { Bubble, BubbleContent, BubbleGroup, BubbleReactions } from "@bext-stack/ui/primitives"

// Bulle simple
<Bubble variant="muted">
  <BubbleContent>Hey there! What's up?</BubbleContent>
</Bubble>

// Bulle avec reaction
<Bubble variant="muted">
  <BubbleContent>Alright, let me take a look.</BubbleContent>
  <BubbleReactions aria-label="Réaction : pouce en l'air">
    <span>👍</span>
  </BubbleReactions>
</Bubble>

// Groupe de bulles du même auteur
<BubbleGroup>
  <Bubble variant="muted"><BubbleContent>Coucou.</BubbleContent></Bubble>
  <Bubble variant="muted"><BubbleContent>Ça va ?</BubbleContent></Bubble>
</BubbleGroup>

Anatomy

<Bubble variant="default|secondary|muted|tinted|outline|ghost|destructive" align="start|end">
  ├── <BubbleContent>text or rich content</BubbleContent>
  └── <BubbleReactions>       // optionnel, absolute badge
      └── <span>👍</span>   // emoji ou <button>

<BubbleGroup>                 // empile 2+ Bubble du même auteur
  ├── <Bubble>...</Bubble>
  └── <Bubble>...</Bubble>

API Reference

Bubble

Prop Type Default Description
variant "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" "default" Détermine le fond, la couleur du texte, la présence d'une bordure et du padding.
align "start" | "end" "start" Alignement de la bulle dans son parent flex. Utilisé principalement quand Bubble est direct child d'un container flex (sans Message).
className string Classes CSS supplémentaires sur le root
.

BubbleContent

Prop Type Default Description
className string Wrapper sémantique du contenu (span). Pas de style propre — le padding/bg vit sur Bubble.

BubbleGroup

Prop Type Default Description
className string Container flex-col gap-2 pour empiler plusieurs Bubble du même auteur.

BubbleReactions

Prop Type Default Description
side "top" | "bottom" "bottom" Position verticale relative à la bulle. "bottom" → sous, "top" → au-dessus.
align "start" | "end" "end" Alignement horizontal. "start" → left-3, "end" → right-3.
className string Classes CSS supplémentaires. Le badge est absolute z-10 avec bg-muted + ring-white.

Data Attributes

Attributs qu'on peut cibler en CSS custom pour override le style par état.

Attribute On Values Description
data-slot chaque sub-composant "bubble" | "bubble-content" | "bubble-group" | "bubble-reactions" Sélecteur stable pour du styling custom.
data-variant Bubble "default" | "secondary" | ... Reflète la prop variant. Utile pour custom overrides via [data-variant=X] { ... }.
data-align Bubble + BubbleReactions "start" | "end" Reflète la prop align. Pour cibler les cas d'alignement en CSS custom.
data-side BubbleReactions "top" | "bottom" Position verticale.

Keyboard

Key Action
Tab Focus le premier bouton dans BubbleReactions (si présent).
Enter / Space Active le bouton focus dans une reaction.

Accessibility

Une Bubble seule est un container visuel — pas de rôle ARIA. Le texte est directement lisible par les AT.

BubbleReactions avec un emoji doit porter un aria-label descriptif car l'emoji seul n'est pas suffisant. Exemple : <BubbleReactions aria-label="Réaction : pouce en l'air"><span>👍</span></BubbleReactions>. Les boutons de réaction doivent aussi porter chacun leur aria-label.

Le variant destructive utilise text-destructive qui a un contraste suffisant en light + dark mode (validé WCAG AA sur les tokens shadcn). Vérifier si tu override la palette.

Pour un thread complet accessible, envelopper les Bubbles dans un <Message> avec avatar (identification de l'auteur) et poser role="log" aria-live="polite" sur le container du thread.

Examples

Variants

Default (primary bg — messages envoyés)
Secondary (fond secondaire)
Muted (messages reçus — le plus courant)
Tinted (accent adouci)
Outline (border, look email)
Ghost (aucun style — notes assistant)
Destructive (erreurs / warnings)
tsx
<Bubble><BubbleContent>Default</BubbleContent></Bubble>
<Bubble variant="secondary"><BubbleContent>Secondary</BubbleContent></Bubble>
<Bubble variant="muted"><BubbleContent>Muted</BubbleContent></Bubble>
<Bubble variant="tinted"><BubbleContent>Tinted</BubbleContent></Bubble>
<Bubble variant="outline"><BubbleContent>Outline</BubbleContent></Bubble>
<Bubble variant="ghost"><BubbleContent>Ghost</BubbleContent></Bubble>
<Bubble variant="destructive"><BubbleContent>Destructive</BubbleContent></Bubble>

Alignment (start / end)

align="start" — collé à gauche
align="end" — collé à droite
tsx
<Bubble align="start" variant="muted">
  <BubbleContent>Reçu</BubbleContent>
</Bubble>
<Bubble align="end">
  <BubbleContent>Envoyé</BubbleContent>
</Bubble>

BubbleGroup — empiler du même auteur

Salut !
Tu vois mes 2 bulles collées ?
Comme un train de messages sur iMessage.
tsx
<BubbleGroup>
  <Bubble variant="muted"><BubbleContent>Salut !</BubbleContent></Bubble>
  <Bubble variant="muted"><BubbleContent>Tu vois mes 2 bulles collées ?</BubbleContent></Bubble>
  <Bubble variant="muted"><BubbleContent>Comme un train de messages.</BubbleContent></Bubble>
</BubbleGroup>

BubbleReactions

Alright, let me take a look.
👍
You crushed it 🎉
❤️
tsx
// Défaut : side="bottom" align="end"
<Bubble variant="muted">
  <BubbleContent>Alright, let me take a look.</BubbleContent>
  <BubbleReactions aria-label="Réaction : pouce en l'air">
    <span>👍</span>
  </BubbleReactions>
</Bubble>

// Reaction en haut à gauche :
<Bubble align="end">
  <BubbleContent>You crushed it 🎉</BubbleContent>
  <BubbleReactions side="top" align="start" aria-label="Réaction : cœur">
    <span>❤️</span>
  </BubbleReactions>
</Bubble>

Ghost (assistant long-form)

Ghost bubbles s'intègrent au flow textuel sans encadrement. Utile pour les longues notes assistant qui doivent se lire comme du texte, pas comme un message individuel dans une conversation.
tsx
<Bubble variant="ghost">
  <BubbleContent>
    Ghost bubbles s'intègrent au flow textuel sans encadrement.
    Utile pour les longues notes assistant.
  </BubbleContent>
</Bubble>