bx

Message

A single chat turn with a role avatar and a bubble styled per sender.

@meME
Deploying to prod real quick.
@rabbitR
It's 4:55 PM. On a Friday.
@meME
It's a one-line change.
Delivered
@rabbitR
It's always a one-line change 😭.
Alright, let me take a look.
👍
Oliver is typing…
tsx
<div className="flex w-full max-w-sm flex-col gap-6 py-12">
  <Message align="end">
    <MessageAvatar>
      <Avatar><AvatarImage src="/avatars/10.png" alt="@me" /><AvatarFallback>ME</AvatarFallback></Avatar>
    </MessageAvatar>
    <MessageContent>
      <Bubble><BubbleContent>Deploying to prod real quick.</BubbleContent></Bubble>
    </MessageContent>
  </Message>
  <Message>
    <MessageAvatar>
      <Avatar><AvatarImage src="/avatars/02.png" alt="@rabbit" /><AvatarFallback>R</AvatarFallback></Avatar>
    </MessageAvatar>
    <MessageContent>
      <Bubble variant="muted"><BubbleContent>It's 4:55 PM. On a Friday.</BubbleContent></Bubble>
    </MessageContent>
  </Message>
  <Message align="end">
    <MessageAvatar>…</MessageAvatar>
    <MessageContent>
      <Bubble><BubbleContent>It's a one-line change.</BubbleContent></Bubble>
      <MessageFooter>Delivered</MessageFooter>
    </MessageContent>
  </Message>
  <Message>
    <MessageAvatar>…</MessageAvatar>
    <MessageContent>
      <BubbleGroup>
        <Bubble variant="muted"><BubbleContent>It's always a one-line change 😭.</BubbleContent></Bubble>
        <Bubble variant="muted">
          <BubbleContent>Alright, let me take a look.</BubbleContent>
          <BubbleReactions aria-label="Reactions: thumbs up"><span>👍</span></BubbleReactions>
        </Bubble>
      </BubbleGroup>
    </MessageContent>
  </Message>
  <Marker role="status">
    <MarkerContent className="shimmer"><span className="font-medium">Oliver</span> is typing...</MarkerContent>
  </Marker>
</div>

Message représente un tour de conversation (chat). Il n'affiche rien par lui-même : c'est un contrôleur d'alignement (align="start" ou "end") qui pose ses enfants côte à côte via flex et inverse l'ordre pour les messages envoyés (align="end"flex-row-reverse).

Un Message se compose typiquement de deux éléments frères :

  • MessageAvatar — l'avatar rond qui identifie l'auteur. Contient un <Avatar> avec AvatarImage + AvatarFallback. Optionnel : sans avatar, la bulle prend toute la largeur.
  • MessageContent — la colonne verticale qui empile MessageHeader (nom / horodatage), la Bubble avec son BubbleContent, et MessageFooter (statut, actions inline comme "Delivered" ou boutons "Undo"/"Retry").

La Bubble est le rectangle coloré du message. Six variantes sémantiques : default (accent primary, pour l'utilisateur courant), muted (gris neutre, pour les interlocuteurs), tinted (teinté primary léger), outline (fond neutre + bordure), ghost (aucun style — pour les longues notes assistant), destructive (rouge, pour les erreurs / warnings). BubbleContent est le vrai conteneur du texte ; BubbleReactions se pose en absolute par-dessus (émojis / réactions).

Pour empiler plusieurs bulles du même auteur, enveloppez-les dans un BubbleGroup à l'intérieur de MessageContent — un seul avatar est affiché pour tout le groupe.

Pour indiquer un état système (bot qui réfléchit, chargement, note de statut), utilisez Marker à la place de MessageContent<Marker role="status"><MarkerIcon><Spinner /></MarkerIcon><MarkerContent>Checking the logs...</MarkerContent></Marker>.

Installation

Terminal
$ bext ui add message

Usage

tsx
import {
  Message,
  MessageAvatar,
  MessageContent,
  MessageFooter,
  MessageGroup,
  MessageHeader,
} from "@bext-stack/ui/primitives"
import { Avatar, AvatarFallback, AvatarImage } from "@bext-stack/ui/primitives"
import { Bubble, BubbleContent, BubbleGroup, BubbleReactions } from "@bext-stack/ui/primitives"

<Message align="end">
  <MessageAvatar>
    <Avatar>
      <AvatarImage src="/avatars/10.png" alt="@me" />
      <AvatarFallback>ME</AvatarFallback>
    </Avatar>
  </MessageAvatar>
  <MessageContent>
    <Bubble>
      <BubbleContent>Hello world.</BubbleContent>
    </Bubble>
  </MessageContent>
</Message>

Anatomy

<Message align="start | end">
  ├── <MessageAvatar>              // optionnel — l'avatar rond
  │   └── <Avatar>...</Avatar>
  └── <MessageContent>             // colonne verticale
      ├── <MessageHeader>...       // nom + horodatage (optionnel)
      ├── <Bubble variant="...">   // la bulle stylée par variant
      │   ├── <BubbleContent>      // le texte du message
      │   └── <BubbleReactions>    // réactions emojis (optionnel)
      └── <MessageFooter>...       // statut + actions (optionnel)

<MessageGroup>                     // wrapper pour empiler plusieurs Message du même auteur
  ├── <Message>...</Message>
  └── <Message>...</Message>

API Reference

Message

Prop Type Default Description
align "start" | "end" "start" Alignement du message. "end" déclenche flex-row-reverse (avatar à droite, bulle collée à droite) — typiquement pour les messages envoyés.
className string Classes CSS supplémentaires sur le root
.
children ReactNode Enfants : typiquement MessageAvatar + MessageContent.

MessageGroup

Prop Type Default Description
className string Wrapper qui empile plusieurs du même auteur avec un gap-2.

MessageAvatar

Prop Type Default Description
className string Container de l'avatar. min-w-8 shrink-0 self-end + translate-y-8 quand un MessageFooter est présent (pour rester aligné avec la bulle).
children ReactNode Typiquement un avec AvatarImage + AvatarFallback.

MessageContent

Prop Type Default Description
className string Colonne verticale qui empile Header + Bubble + Footer avec gap-2.5.

MessageHeader

Prop Type Default Description
className string Ligne au-dessus de la bulle (nom d'auteur, horodatage). text-xs muted-foreground.

MessageFooter

Prop Type Default Description
className string Ligne sous la bulle (statut "Delivered", actions "Undo"/"Retry"). Aligné à droite quand align="end".

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 "message" | "message-group" | "message-avatar" | "message-content" | "message-header" | "message-footer" Sélecteur stable pour du styling custom.
data-align Message "start" | "end" Reflète la prop align. Utilisé par MessageFooter pour justify-end en align=end.

Keyboard

Key Action
Tab Focus next interactive element (avatar link, footer button).
Enter / Space Activate button/link in header/footer.

Accessibility

Le composant Message n'a pas de rôle ARIA spécifique — c'est un container visuel. C'est aux enfants (Avatar, buttons de MessageFooter) de porter leurs propres aria-label et rôles quand nécessaire.

Pour un thread de messages, envelopper dans un conteneur avec role="log" et aria-live="polite" permet aux lecteurs d'écran d'annoncer les nouveaux messages sans interruption. Utiliser MessageScroller qui pose déjà les bons attributs.

Les BubbleReactions sont interactives : leur wrapper doit avoir aria-label="Reactions" et chaque bouton un aria-label décrivant la réaction ("Thumbs up", "Copy", ...). L'emoji seul n'est pas suffisant.

Le Marker pour les états système (spinner "Checking...") doit avoir role="status" pour être annoncé aux ATs comme un changement d'état non urgent.

Examples

Avatar

@shadcnCN
Something went wrong. Any idea?
@evilrabbitER
I checked the latest deployment and found the build failed during dependency installation.
@evilrabbitER
Something went wrong. Any idea?
@shadcnCN
I checked the latest deployment and found the build failed during dependency installation.
Can you share the exact error message from the logs?
CN
Something went wrong. Any idea?
tsx
<div className="flex w-full max-w-md flex-col gap-10">
  <Message>
    <MessageAvatar>
      <Avatar>
        <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
        <AvatarFallback>CN</AvatarFallback>
      </Avatar>
    </MessageAvatar>
    <MessageContent>
      <Bubble variant="muted"><BubbleContent>Something went wrong. Any idea?</BubbleContent></Bubble>
    </MessageContent>
  </Message>
  <Message align="end">
    <MessageAvatar><Avatar>…</Avatar></MessageAvatar>
    <MessageContent>
      <Bubble variant="muted"><BubbleContent>Something went wrong. Any idea?</BubbleContent></Bubble>
    </MessageContent>
  </Message>
  <Message>
    <MessageAvatar><Avatar><AvatarFallback>CN</AvatarFallback></Avatar></MessageAvatar>
    <MessageContent>
      <Bubble variant="destructive"><BubbleContent>Something went wrong. Any idea?</BubbleContent></Bubble>
    </MessageContent>
  </Message>
</div>

Group

I checked the registry addresses.
The component and example JSON now live under the UI registry.
I checked the registry addresses.
The component and example JSON now live under the UI registry.
I checked the registry addresses.
@shadcnCN
The component and example JSON now live under the UI registry.
I checked the registry addresses.
@shadcnCN
The component and example JSON now live under the UI registry.
tsx
<MessageGroup>
  <Message>
    <MessageContent>
      <Bubble variant="muted"><BubbleContent>I checked the registry addresses.</BubbleContent></Bubble>
    </MessageContent>
  </Message>
  <Message>
    <MessageAvatar>
      <Avatar><AvatarImage src="…" /><AvatarFallback>CN</AvatarFallback></Avatar>
    </MessageAvatar>
    <MessageContent>
      <Bubble variant="muted"><BubbleContent>The component and example JSON now live under the UI registry.</BubbleContent></Bubble>
    </MessageContent>
  </Message>
</MessageGroup>

Group Chat

Why is the dashboard showing zero users
Define "zero."
@evilrabbitER
What do you mean?
@eve can you check?
Checking the logs…
Eve
I found the following error in the logs: - Error: No users found - Stack trace: - at src/users/services/user-service.ts:123 - at src/users/services/get-user.ts:230 Can you check if the users are being created?
tsx
<Message align="end">
  <MessageContent>
    <Bubble variant="tinted"><BubbleContent>Why is the dashboard showing zero users</BubbleContent></Bubble>
  </MessageContent>
</Message>
<Message>
  <Marker role="status">
    <MarkerIcon><Spinner /></MarkerIcon>
    <MarkerContent className="shimmer">Checking the logs…</MarkerContent>
  </Marker>
</Message>
<Message>
  <MessageContent>
    <MessageHeader>Eve</MessageHeader>
    <Bubble variant="ghost">
      <BubbleContent>...</BubbleContent>
    </Bubble>
  </MessageContent>
</Message>
You
Can we keep this style quiet and send the update today?
Delivered
Olivia1m ago
I checked the logs. The retry finished and the missing invoices are included now.
From Support queue
Marina
I drafted the reply with the exact filter path and a link to the export history.
Draft ready
Assistant note
Ghost messages keep long-form assistant notes aligned with the surrounding text instead of adding a frame.
Internal only
Automation warning
The old export link expires in 10 minutes. Regenerate before sending this transcript externally.
Needs action
tsx
<Message align="end">
  <MessageContent>
    <MessageHeader className="justify-end">You</MessageHeader>
    <Bubble><BubbleContent>...</BubbleContent></Bubble>
    <MessageFooter className="gap-2">
      <span>Delivered</span>
      <Button variant="ghost" size="xs">Undo</Button>
    </MessageFooter>
  </MessageContent>
</Message>

Actions

What is the status of the deployment?
@evilrabbitER
The install failure is coming from the workspace package. I can open the exact log line or retry the deployment.
Okay drop me a link. I'll check it out.
Failed to send
tsx
<Message align="end">
  <MessageContent>
    <Bubble><BubbleContent>What is the status of the deployment?</BubbleContent></Bubble>
    <MessageFooter>
      <Button variant="ghost" size="icon"><CopyIcon /></Button>
      <Button variant="ghost" size="icon"><RefreshCcwIcon /></Button>
    </MessageFooter>
  </MessageContent>
</Message>

Attachment

Workspace
Here's the image. Can you add it to the PDF? Use it for the cover page.
Done. Here's the PDF with the image added as the cover page.
sales-dashboard.pdfPDF · 2.4 MB
Thanks. Looks good.
tsx
<Message align="end">
  <MessageContent>
    <Attachment orientation="vertical">
      <AttachmentMedia variant="image"><img src="..." /></AttachmentMedia>
    </Attachment>
    <Bubble>
      <BubbleContent>Here's the image.</BubbleContent>
    </Bubble>
  </MessageContent>
</Message>

Attachment Group

Photo 1
Photo 2
Photo 3
Here are the brand photos. Can you add them to the deck?
Done. Here's the draft deck and the exported assets.
brand-deck.pdfPDF · 3.1 MB
brand-deck.keyKeynote · 9 MB
brand-assets.zipZIP · 4.2 MB
tsx
<Message align="end">
  <MessageContent>
    <AttachmentGroup className="w-[80%]">
      {photos.map(url => (
        <Attachment orientation="vertical" key={url}>
          <AttachmentMedia variant="image"><img src={url} /></AttachmentMedia>
        </Attachment>
      ))}
    </AttachmentGroup>
    <Bubble>
      <BubbleContent>Here are the brand photos.</BubbleContent>
    </Bubble>
  </MessageContent>
</Message>