Skip to content

ChatBubble

A chat UI component supporting three message role variants. Bold-bordered bubbles with shadow offsets give the conversation interface a distinctive personality.

Demo

Preview
对话开始于 2025-06-01
AL
Alex
嘿!这个新粗野主义 UI 库看起来太赞了!
14:01
对吧!粗边框 + 硬朗阴影,直接把普通组件卷死。
14:02
AL
Alex
Kanban + TreeView 这两个复合组件是重头戏 🔥
14:03
还有 Carousel!Embla 驱动的,性能拉满 💪
14:03

颜色(仅 sent 生效)

AL
Alex
received 不受 color 影响
14:00
default 配色
14:01
primary 配色
14:02
accent 配色
14:03

尺寸

sm 小号气泡
14:00
default 默认气泡
14:01
lg 大号气泡
14:02

Installation

pnpm dlx brutx-vue@latest add chat-bubble

Usage

vue
<script setup>
import { ChatBubble } from 'brutx-ui-vue'

const message = {
    id: '1',
    variant: 'received',
    name: 'Alex',
    content: 'This UI library is amazing!',
    timestamp: '14:01',
}
</script>

<template>
    <div class="flex flex-col gap-3">
        <ChatBubble :message="{ id: '1', variant: 'received', name: 'Alex', content: 'Hello!' }" />
        <ChatBubble :message="{ id: '2', variant: 'sent', content: 'Hey!' }" />
        <ChatBubble :message="{ id: '3', variant: 'system', content: 'Conversation started' }" />
    </div>
</template>

Color Variants

The color prop switches the background color scheme for variant="sent" bubbles, combining orthogonally with variant (e.g., variant="sent" color="accent"). received and system bubbles are not affected by color.

vue
<script setup>
import { ChatBubble } from 'brutx-ui-vue'
</script>

<template>
    <div class="flex flex-col gap-3">
        <ChatBubble :message="{ id: '1', variant: 'sent', content: 'Default color', timestamp: '14:00' }" color="default" />
        <ChatBubble :message="{ id: '2', variant: 'sent', content: 'Primary color', timestamp: '14:01' }" color="primary" />
        <ChatBubble :message="{ id: '3', variant: 'sent', content: 'Accent color', timestamp: '14:02' }" color="accent" />
    </div>
</template>

Variants

Message Roles

VariantStylePurpose
receivedLeft-aligned, background colorMessages from others
sentRight-aligned, primary color backgroundMessages sent by yourself
systemCentered, italic, dashed border, no shadow, forced text-xsSystem notifications

Note: The system variant ignores the size prop and always uses text-xs. It also hides the avatar and sender name.

Colors

ColorDescription
defaultDefault color scheme; sent bubbles use primary color (brutal-primary) background
primaryPrimary color scheme; sent bubbles use primary color (brutal-primary) background with primary color shadow (shadow-brutal-primary)
accentAccent color scheme; sent bubbles use accent color (brutal-accent) background

Sizes

The size prop controls bubble padding, text size, and avatar dimensions simultaneously.

SizeBubble PaddingText SizeAvatar Size
smpx-3 py-1.5text-xsw-6 h-6
defaultpx-4 py-2.5text-smw-8 h-8
lgpx-5 py-3.5text-basew-10 h-10
vue
<script setup>
import { ChatBubble } from 'brutx-ui-vue'
</script>

<template>
    <div class="flex flex-col gap-3">
        <ChatBubble :message="{ id: '1', variant: 'sent', name: 'Me', content: 'Small bubble', timestamp: '14:00' }" size="sm" />
        <ChatBubble :message="{ id: '2', variant: 'sent', name: 'Me', content: 'Default bubble', timestamp: '14:01' }" size="default" />
        <ChatBubble :message="{ id: '3', variant: 'sent', name: 'Me', content: 'Large bubble', timestamp: '14:02' }" size="lg" />
    </div>
</template>

Sub-components

ChatContainer

A chat message container with time-based grouping support:

vue
<script setup>
import { ChatContainer } from 'brutx-ui-vue'

const messages = [
    { id: '1', variant: 'received', name: 'Alex', content: 'Hello!', timestamp: new Date() },
    { id: '2', variant: 'sent', content: 'Hey!', timestamp: new Date(), status: 'read' },
]
</script>

<template>
    <ChatContainer :messages="messages" group-by-time show-status />
</template>

Data Types

ChatMessage

ts
interface ChatMessage {
    id: string                        // Unique message identifier (required)
    content: string                   // Message text content (required)
    variant?: 'sent' | 'received' | 'system'  // Message variant, defaults to 'received'
    avatar?: string                   // Avatar image URL; falls back to initials on load failure
    name?: string                     // Sender name, used for display and initial generation
    timestamp?: string | Date         // Timestamp; strings are displayed directly, Date objects are formatted via dateFormat or toLocaleString
    status?: MessageStatus            // Message status, only effective for variant="sent"
}

MessageStatus

ts
type MessageStatus = 'sending' | 'sent' | 'delivered' | 'read' | 'failed'
StatusIconStyle
sendingSpinning loader iconSemi-transparent + rotation animation
sentSingle checkSemi-transparent
deliveredDouble checkSlightly darker
readDouble checkTheme primary color
failedWarning iconDestructive color

Note: status is a closed union type at the type level, but message data usually comes from backend APIs and may carry values outside the enum at runtime; in that case ChatBubble silently ignores the status (no status icon is rendered) without throwing an error.

Props

ChatBubble

PropTypeDefaultDescription
messageChatMessageMessage data object (required)
color'default' | 'primary' | 'accent''default'Background color scheme for sent bubbles; only effective for variant="sent", received/system are unaffected
size'sm' | 'default' | 'lg''default'Bubble padding/text size, also adjusts avatar size
showAvatarbooleantrueWhether to show the avatar area (system messages always hidden)
showStatusbooleantrueWhether to show the message status icon (sent messages only)
showTimestampbooleantrueWhether to show the timestamp
dateFormat(date: Date) => stringCustom date formatting function; uses Date.toLocaleString() when unset
classstringCustom CSS class for the bubble

ChatContainer

PropTypeDefaultDescription
messagesChatMessage[]Message array (required)
groupByTimebooleanfalseWhether to group by time (today/yesterday/date)
groupIntervalnumber5Time grouping interval (minutes) within the same date, clamped to a minimum of 1; adjacent messages whose gap exceeds this interval (strictly >) are split into a new group
showAvatarbooleantrueWhether to show avatars
showStatusbooleantrueWhether to show message status
showTimestampbooleantrueWhether to show timestamps
dateFormat(date: Date) => stringCustom date formatting function, also used for group date labels (non today/yesterday) and interval-split group time labels
classstringCustom CSS class

Note: When groupByTime is true:

  • Messages with a valid timestamp are sorted by timestamp ascending, so unordered input still yields continuous, non-jumping date groups; messages without a valid timestamp (missing, or display-only strings like '14:30', 'yesterday' that cannot be parsed) stay anchored at their original index — they do not sink to the end and keep their relative order;
  • Group boundaries are compared by the real calendar date (year/month/day), decoupled from the display string produced by dateFormat: even if a custom format outputs a time-only dimension (e.g., HH:mm), messages from different days are never merged and same-day messages are never split into separate date groups;
  • Groups are separated by dividers and date labels (today/yesterday/specific date);
  • Within the same date, adjacent messages whose time gap exceeds groupInterval minutes are further split into separate groups that show the specific time (HH:mm, customizable via dateFormat), without repeating the date label.

Slots

SlotScopeDescription
defaultCustom bubble content (defaults to displaying message.content)

Accessibility

  • ARIA Attributes: ChatContainer uses role="log" for accessibility support
  • Semantics: Message bubbles use semantic structure, supporting correct screen reader narration

Brute force builds.