120k

Bubble

以消息气泡的形式显示对话内容。支持变体、对齐、分组、表情反馈和可折叠内容。

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.
Sure. Hit me with your best demo.
Yes. You are reading a demo that is demoing itself. Very meta. Very on-brand.
import {
  Bubble,
  BubbleContent,

Bubble 组件用于显示带边框的对话内容。可用于聊天文本、简短的结构化输出、引用回复、建议和表情反馈。

对于功能完整的聊天界面,请使用 Message 组件。Bubble 有意仅聚焦于气泡表面。请将头像、名称、时间戳、元数据以及消息级操作放在 Message 中。

安装

pnpm dlx shadcn@latest add bubble

用法

import { Bubble, BubbleContent, BubbleReactions } from "@/components/ui/bubble"
<Bubble>
  <BubbleContent>
    我检查了注册表输出并移除了过时的路由。
  </BubbleContent>
  <BubbleReactions>
    <span>👍</span>
  </BubbleReactions>
</Bubble>

组成

使用以下组合来构建气泡:

Bubble
├── BubbleContent
└── BubbleReactions

使用 BubbleGroup 来将来自同一发送者的连续气泡分组:

BubbleGroup
├── Bubble
│   └── BubbleContent
└── Bubble
    └── BubbleContent

特性

  • 七种视觉变体,从强烈的主色气泡到无边框的幽灵内容
  • 发件人和收件人气泡的起始和结束对齐
  • 锚定在气泡边缘的反应,可配置侧边和对齐方式
  • 气泡会根据其内容自适应大小,最多占容器宽度的 80%
  • 通过 asChild 实现多态内容,用于链接和按钮气泡
  • 可通过每个部分上的 className 属性进行自定义样式设置

示例

变体

使用 variant 来改变气泡的视觉样式。

This is the default primary bubble.
This is the secondary variant.
This one is muted. It uses a lower emphasis color for the chat bubble.
This one is tinted. The tint is a softer color derived from the primary color.
We can also use an outlined variant.
Or a destructive variant with a reaction.

Ghost bubbles work for assistant text, markdown, and other content that should not be framed.

This is perfect for assistant messages that should not have a frame and can take the full width of the container. You can also render code in it.

Ghost bubbles are full width and can take the full width of the container.

import { Markdown } from "@/components/markdown"
import {
  Bubble,
变体描述
default强调的主气泡,通常用于当前用户。
secondary用于对话内容的标准中性气泡。
muted强调较低的气泡,用于安静的辅助内容。
tinted带有轻微主色调的气泡。
outline带边框的气泡,用于次要或富内容。
ghost无边框内容,用于助手文本或富内容。
destructive用于错误或失败操作的破坏性气泡。

气泡会根据内容自适应大小,最大不超过容器宽度的 80%。ghost 变体会移除最大宽度限制,因此助手文本和富内容可以占满整行。

对齐

Bubble 上使用 align 将气泡对齐到对话的开始或结尾。

This bubble is aligned to the start. This is the default alignment.
This bubble is aligned to the end. Use this for user messages.
import { Bubble, BubbleContent } from "@/components/ui/bubble"

export function BubbleAlignmentDemo() {
align描述
start将气泡对齐到对话的开始处。
end将气泡对齐到对话的结尾处。

注意: 在构建聊天界面时,你可能希望在 Message 组件本身上使用对齐,而不是在 Bubble 组件上。你可以在 Message 组件上使用 role 属性,自动将气泡对齐到对话的开始或结尾。

气泡组

使用 BubbleGroup 将来自同一发送者的连续气泡分组。请注意,align 属性应设置在 Bubble 组件本身上,而不是 BubbleGroup 组件上。

BubbleGroup
├── Bubble
│   └── BubbleContent
└── Bubble
    └── BubbleContent
Can you tell me what's the issue?
You tell me!
It worked yesterday. You broke it!
Find the bug and fix it.
👀
Want me to diff yesterday's you against today's you? It's a bit embarrassing.
import {
  Bubble,
  BubbleContent,

链接和按钮

你可以通过在 BubbleContent 上使用 asChild 属性,将气泡变成链接或按钮。

How can I help you today?
"use client"

import { toast } from "sonner"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
 
export function BubbleLinkDemo() {
  return (
    <Bubble variant="muted">
      <BubbleContent asChild>
        <button>点击这里</button>
      </BubbleContent>
    </Bubble>
  )
}

反应

使用 BubbleReactions 来显示气泡反应。你可以用它来展示反应或快速操作按钮。使用 sidealign 来定位这一行——side="top" 会将其锚定到上边缘。反应会与气泡边缘重叠,因此行与行之间需要留出垂直空间——下面的示例因此使用了更大的 gap

I don't need tests, I know my code works.
Bold. Fine I'll add some tests. I'll let you know when they're done.
Tests passed on the first try. All 142 of them. Looking good!
Are you sure I can run this command?
"use client"

import { toast } from "sonner"

展开更多 / 可折叠

较长的气泡内容可以配合 Collapsible 组合使用,以实现“展开更多”或“收起更多”的交互。使用 CollapsibleTrigger 组件来触发可折叠内容。

How can I help you today?
The accessibility review found two focus states that were visually too subtle in dark mode. I checked the dialog, menu, and drawer paths because each one renders focusable control...
"use client"

import * as React from "react"

提示框

将气泡包裹在 Tooltip 中,可在悬停时显示元数据,例如消息何时被读取。

Did you remove the stale route?
Yes, removed it from the registry.
import { CheckIcon } from "lucide-react"

import {

弹出框

将气泡与 Popover 搭配使用,以便按需显示更多信息,例如失败操作的完整错误消息。

Run the build script.
Failed to run the command.
import { InfoIcon } from "lucide-react"

import {

无障碍

Bubble 渲染的是展示性消息表面。请将会话级语义保留在周围容器上,并遵循以下指南。

为反应添加标签

反应会以一行 emoji 的形式渲染。屏幕阅读器会在没有上下文的情况下逐个读取每个字形,而像 +8 这样的计数会被读作“加八”。请将这一行作为带有描述性 aria-label 的单个图像进行分组,这样它只会播报一次。role="img" 还会将单个 emoji 对辅助技术隐藏,因此不需要 aria-hidden

<BubbleReactions role="img" aria-label="反应:点赞、火焰,以及另外 8 个">
  <span>👍</span>
  <span>🔥</span>
  <span>+8</span>
</BubbleReactions>

当反应是可交互时,请改为渲染按钮,并为仅图标按钮添加 aria-label

<BubbleReactions>
  <Button aria-label="点赞" variant="secondary" size="icon-xs">
    <ThumbsUpIcon />
  </Button>
</BubbleReactions>

可交互的气泡

当气泡可点击时,请使用真正的 <button><a> 并配合 asChild 属性来渲染,这样它才能获得焦点并暴露正确的角色。BubbleContent 已为可交互元素提供可见的焦点环,可访问名称则来自气泡文本。不需要额外的标签。

<Bubble variant="muted" align="end">
  <BubbleContent asChild>
    <button type="button" onClick={onReply}>
      我忘记了我的密码
    </button>
  </BubbleContent>
</Bubble>

不仅仅依赖颜色来表达含义

Bubble 变体通过颜色传达角色和语气。请将其与文本、对齐方式或图标搭配使用,这样含义就不会只靠颜色传达。对于 destructive 气泡,请将错误上下文保留在消息文本中,而不是依赖颜色样式。

API 参考

Bubble

根气泡包装器。

属性类型默认值描述
variant"default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive""default"气泡的视觉样式。
align"start" | "end""start"气泡的行内对齐方式。
classNamestring-应用于根元素的附加类。

BubbleContent

气泡内容包装器。

属性类型默认值描述
asChildbooleanfalse将内容渲染为子元素。
classNamestring-应用于内容元素的附加类。

BubbleReactions

显示气泡上重叠的表情反应。

属性类型默认值描述
side"top" | "bottom""bottom"将表情反应锚定在气泡的哪一侧。
align"start" | "end""end"表情反应的行内对齐方式。
classNamestring-应用于反应行的附加类。

BubbleGroup

将来自同一发送者的连续气泡分组。

属性类型默认值描述
classNamestring-应用于组根元素的附加类。