ChatBubble
One message in a conversation. The avatar, the sender's name, the time, the delivery mark, the media above the text and the link card below it are each drawn only when they are given something.
import { Avatar, ChatBubble } from 'neba';
<ChatBubble avatar={<Avatar name="Jane Doe" size="sm" />} name="Jane" time="09:41">
Are we still on for the review at 3?
</ChatBubble>
<ChatBubble side="end" variant="solid" status="read">
Yes — I pushed the branch just now.
</ChatBubble>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| side | 'start' | 'end' | 'start' | Whose message this is. Decides which way the row runs and which corner of the sheet is cut short |
| name | ReactNode | — | Who sent it, above the bubble |
| time | ReactNode | — | When it was sent, beside the name |
| avatar | ReactNode | — | The sender’s picture: an Avatar. Left out, the bubble takes the whole row |
| status | 'sending' | 'sent' | 'delivered' | 'read' | 'failed' | — | How far the message has got, drawn as a mark under the bubble. Left out, nothing is drawn |
| statusLabel | string | — | Overrides the word the mark is read out as |
| typing | boolean | false | Draws the three dots instead of the message. children is left alone, so the same bubble can go back to it |
| media | ReactNode | — | A picture, a video, a map: drawn edge to edge above the text, cropped by the bubble’s corners |
| preview | ChatBubbleLinkPreview | — | A link in the message, unfurled into a card: url · title · description · image · site · newTab |
| actions | ReactNode | — | The message’s own actions: usually a Menu trigger. Sits beside the bubble and stays out of the way until the row is hovered or focused |
| locale | string | 'en' | Which language the marks are read out in: a BCP 47 tag. Unsupported tags fall back to English |
| variantshared | 'solid' | 'outline' | 'text' | 'outline' | Weight of the bubble’s surface. solid is the usual way to tell your own messages apart; side deliberately does not decide it |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Height and type scale |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | 'default' | 'compact' | 'default' | Padding inside the bubble, and nothing else |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. 0 means no shadow at all |
| children | ReactNode | — | The message |
Every other <div> attribute passes through to the row, except title: a bubble has no headline, and the browser's tooltip on a whole message is rarely what anyone wants.
The shared axes (variant size color density elevation) are defined in prop conventions.
Examples
side
start is the default and end is the other party. It runs the row the other way (avatar, bubble and actions all flip), and cuts the corner nearest the speaker short, which is what says where the message came from without hanging a tail off the sheet.
variant is what tells your own messages from everyone else's, and it is deliberately not tied to side: filling the trailing column is a convention, not a law.
avatar, name and time
avatar takes an Avatar at whatever size the thread uses. name and time sit above the bubble as one line; leave both out on a follow-up message and the run reads as one turn rather than three.
status
Five steps: sending, sent, delivered, read and failed. Only the last two carry a colour: a thread where every message is marked in colour is a thread where the colour has stopped meaning anything.
The mark is the whole of what is drawn. The word behind it is read out but never shown; statusLabel replaces it.
typing
typing draws three dots in place of the message. children is left alone, so the same bubble goes back to the message the moment it arrives.
The dots light in sequence and never move: colour is the axis every indeterminate indicator in the library uses, and something bouncing in the corner of a thread being read is exactly what the design language has no time for.
media
media is drawn edge to edge above the text, so the bubble's own corners crop it. An <img> or <video> inside it is stretched to the width of the bubble; anything else is laid out as it comes.
preview
preview unfurls a link into a card under the text: url, title, description, image, site, and newTab for the ones that should leave the app. The card's surface is mixed out of the bubble's own text colour, so it works on a filled bubble and a bare one alike.
actions
actions sits beside the bubble: a Menu trigger, most of the time. It stays out of the way until the row is hovered or something in it takes focus, and is simply always there on a pointer that cannot hover.
locale
The delivery marks and the typing dots are read out as words, and locale is which language those words are in: a BCP 47 tag such as ko, pt-BR or zh-Hant. Tags with no translation fall back to English.
<ChatBubble side="end" status="read" locale="ko">
방금 브랜치 올렸어요
</ChatBubble>Accessibility
- The status mark is a glyph with the word behind it in text only a screen reader reaches. Set
locale, or write the word out instatusLabel. - A ChatBubble is one message, not a thread. Wrap the conversation in the markup the page needs: a list, or a container with
role="log"for one that keeps updating. mediacarries noaltof its own: pass a real<img alt="…">, or an empty one where the text beside it already says what the picture is.