import { Popover, Button } from "@lark-ui/core";
import { BellIcon } from "@phosphor-icons/react";
export function PopoverHeroDemo() {
return (
<Popover.Root>
<Popover.Trigger
render={<Button shape="square" icon={BellIcon} aria-label="通知" />}
/>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>通知</Popover.Title>
<Popover.Description>你已全部看完,干得漂亮!</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}安装
统一导出
import { Popover } from "@lark-ui/core";按需导出
import { Popover } from "@lark-ui/core/components/popover";用法
import { Popover, Button } from "@lark-ui/core";
export default function Example() {
return (
<Popover.Root>
<Popover.Trigger render={<Button />}>Open</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>Popover Title</Popover.Title>
<Popover.Description>Popover content goes here.</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}气泡卡片与工具提示
虽然气泡卡片也可以通过悬停触发(使用 openOnHover),但它的用途与工具提示不同。
了解两者各自的适用场景,对无障碍和用户体验都很重要。
| 工具提示 | 气泡卡片 | |
|---|---|---|
| 用途 | 用于标识的简短、非交互式文本标签 | 富内容、可交互的内容容器 |
| 内容 | 仅纯文本 | 任意内容:链接、按钮、表单、图片 |
| 触发方式 | 悬停或聚焦 | 点击(默认)或悬停 |
| ARIA 角色 |
|
|
| 键盘 | 不可聚焦 | 打开时焦点移入内部并被锁定 |
使用工具提示(Tooltip)当你需要为图标按钮添加标签或提供简短说明时。 使用气泡卡片(Popover)当用户需要与内部内容交互时,例如点击链接、填写表单,或通过按钮关闭。
示例
基础气泡卡片
import { Popover, Button } from "@lark-ui/core";
export function PopoverBasicDemo() {
return (
<Popover.Root>
<Popover.Trigger render={<Button />}>打开气泡卡片</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>气泡卡片标题</Popover.Title>
<Popover.Description>
这是一个包含标题和描述的基础气泡卡片。
</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}已弃用的 Popover.Content 兼容性
Popover.Content 已被弃用,但仍然保留,以免现有气泡卡片失效。它把 Portal、Positioner、
Popup 和 Arrow 合并为一个组件,并且仅通过不透明度实现平滑淡入,不带缩放或位移动画。
新的气泡卡片应像基础示例中那样组合各个独立部分。
import { Popover, Button } from "@lark-ui/core";
export function PopoverLegacyContentDemo() {
return (
<Popover>
<Popover.Trigger render={<Button variant="secondary" />}>
打开旧版 Content
</Popover.Trigger>
<Popover.Content className="w-72">
<Popover.Title>兼容模式</Popover.Title>
<Popover.Description>
这个旧版包装器只做淡入,不做缩放或变换。
</Popover.Description>
</Popover.Content>
</Popover>
);
}可滚动内容
当箭头和标题需要保持固定时,请把溢出的内容放在内部滚动容器中。已弃用的 Popover.Content
上现有的溢出样式仍然受支持,因为它只使用淡入淡出过渡。
import { Popover, Button } from "@lark-ui/core";
export function PopoverOverflowDemo() {
return (
<Popover.Root>
<Popover.Trigger render={<Button variant="secondary" />}>
打开可滚动的气泡卡片
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup className="w-72">
<Popover.Arrow />
<Popover.Title>最近通知</Popover.Title>
<Popover.Description>
列表可滚动,标题和箭头保持固定。
</Popover.Description>
<div
aria-label="最近通知"
className="mt-3 max-h-40 overflow-y-auto rounded-md border border-lark-hairline outline-none focus-visible:ring-2 focus-visible:ring-lark-brand"
tabIndex={0}
>
{Array.from({ length: 10 }, (_, index) => (
<div
key={index}
className="border-b border-lark-hairline px-3 py-2 last:border-b-0"
>
通知 {index + 1}
</div>
))}
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}CSS 变量
Base UI 通过 --available-height 暴露触发元素与视口边缘之间的可用空间。此示例先把弹层的
首选高度设为 320px,再将该变量用作最大高度,这样空间不足时弹层会收缩,内部内容随之滚动。
import { Popover, Button } from "@lark-ui/core";
export function PopoverCssVariablesDemo() {
return (
<Popover.Root>
<Popover.Trigger render={<Button />}>打开较高的气泡卡片</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup className="h-80 max-h-[var(--available-height)] w-72">
<Popover.Arrow />
<Popover.Title>自适应高度的浮层</Popover.Title>
<Popover.Description>
除非视口空间不足,否则该浮层高度为 320px。
</Popover.Description>
<div
aria-label="气泡卡片行"
className="mt-3 min-h-0 flex-1 overflow-y-auto rounded-md border border-lark-hairline outline-none focus-visible:ring-2 focus-visible:ring-lark-brand"
tabIndex={0}
>
{Array.from({ length: 12 }, (_, index) => (
<div
key={index}
className="border-b border-lark-hairline px-3 py-2 last:border-b-0"
>
第 {index + 1} 行
</div>
))}
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}带关闭按钮
import { Popover, Button } from "@lark-ui/core";
export function PopoverWithCloseDemo() {
return (
<Popover.Root>
<Popover.Trigger render={<Button />}>打开设置</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>设置</Popover.Title>
<Popover.Description>在下方配置你的偏好设置。</Popover.Description>
<div className="mt-3">
<Popover.Close render={<Button variant="secondary" size="sm" />}>
关闭
</Popover.Close>
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}定位
在 Popover.Positioner 上使用 side 属性,可以控制气泡卡片相对于触发元素出现的位置。
import { Popover, Button } from "@lark-ui/core";
export function PopoverPositionDemo() {
return (
<div className="flex flex-wrap gap-4">
<Popover.Root>
<Popover.Trigger render={<Button variant="secondary" />}>
底部
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner side="bottom">
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>底部</Popover.Title>
<Popover.Description>
气泡卡片显示在下方(默认)。
</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
<Popover.Root>
<Popover.Trigger render={<Button variant="secondary" />}>
顶部
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner side="top">
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>顶部</Popover.Title>
<Popover.Description>气泡卡片显示在上方。</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
<Popover.Root>
<Popover.Trigger render={<Button variant="secondary" />}>
左侧
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner side="left">
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>左侧</Popover.Title>
<Popover.Description>气泡卡片显示在左侧。</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
<Popover.Root>
<Popover.Trigger render={<Button variant="secondary" />}>
右侧
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner side="right">
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>右侧</Popover.Title>
<Popover.Description>气泡卡片显示在右侧。</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
</div>
);
}自定义内容
气泡卡片可以包含任意内容,包括带头像、按钮等的自定义布局。
import { Popover, Button } from "@lark-ui/core";
export function PopoverCustomContentDemo() {
return (
<Popover.Root>
<Popover.Trigger render={<Button />}>用户资料</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup className="w-64">
<Popover.Arrow />
<div className="flex items-center gap-3">
<div className="size-10 rounded-full bg-lark-recessed" />
<div>
<Popover.Title>简·多伊</Popover.Title>
<p className="text-sm text-lark-subtle">jane@example.com</p>
</div>
</div>
<div className="mt-3 flex gap-2 border-t border-lark-hairline pt-3">
<Button variant="secondary" size="sm" className="flex-1">
个人资料
</Button>
<Popover.Close
render={<Button variant="ghost" size="sm" className="flex-1" />}
>
退出登录
</Popover.Close>
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}悬停打开
在触发元素上使用 openOnHover,可以在用户悬停时打开气泡卡片。你还可以用 delay 指定
气泡卡片出现前的延迟毫秒数。
import { Popover, Button } from "@lark-ui/core";
export function PopoverOpenOnHoverDemo() {
return (
<Popover.Root>
<Popover.Trigger
openOnHover
delay={200}
render={<Button variant="secondary" />}
>
悬停试试
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>悬停触发</Popover.Title>
<Popover.Description>
该气泡卡片在悬停时打开,延迟 200
毫秒。内部仍可包含按钮、链接等可交互内容。
</Popover.Description>
<div className="mt-3">
<Popover.Close render={<Button variant="secondary" size="sm" />}>
知道了
</Popover.Close>
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}虚拟锚点
在 Popover.Positioner 上使用 anchor 属性,可以让气泡卡片相对触发元素之外的元素定位,
或相对一个虚拟点(例如 getBoundingClientRect() 返回的 DOMRect)定位。当触发元素和期望的
锚点位于不同的组件树中时,这会非常有用。
| 名称 | 状态 | |
|---|---|---|
| api-gateway | 运行中 | |
| auth-service | 运行中 | |
| worker-prod | 已暂停 |
import { useState, useRef } from "react";
import { Popover, Button } from "@lark-ui/core";
import { DotsThree } from "@phosphor-icons/react";
/** 锚定到虚拟元素(而非触发器)的 Popover。 */
export function PopoverVirtualAnchorDemo() {
const [selectedRow, setSelectedRow] = useState<string | null>(null);
const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null);
const rowRefs = useRef<Map<string, HTMLTableRowElement>>(new Map());
const rows = [
{ id: "1", name: "api-gateway", status: "运行中" },
{ id: "2", name: "auth-service", status: "运行中" },
{ id: "3", name: "worker-prod", status: "已暂停" },
];
const handleEdit = (id: string) => {
const row = rowRefs.current.get(id);
if (row) {
setAnchorRect(row.getBoundingClientRect());
setSelectedRow(id);
}
};
return (
<div className="w-full">
<div className="overflow-hidden rounded-lg border border-lark-hairline">
<table className="w-full text-sm">
<thead className="bg-lark-elevated">
<tr>
<th className="px-4 py-2 text-left font-medium">名称</th>
<th className="px-4 py-2 text-left font-medium">状态</th>
<th className="w-12 px-4 py-2"></th>
</tr>
</thead>
<tbody className="divide-y divide-lark-hairline">
{rows.map((row) => (
<tr
key={row.id}
ref={(el) => {
if (el) rowRefs.current.set(row.id, el);
}}
className={
selectedRow === row.id ? "bg-lark-recessed" : "bg-lark-base"
}
>
<td className="px-4 py-2 font-mono">{row.name}</td>
<td className="px-4 py-2 text-lark-subtle">{row.status}</td>
<td className="px-4 py-2">
<Button
size="xs"
variant="ghost"
shape="square"
icon={DotsThree}
aria-label={`${row.name} 的操作`}
onClick={() => handleEdit(row.id)}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
<Popover.Root
open={!!selectedRow}
onOpenChange={(open) => !open && setSelectedRow(null)}
>
<Popover.Portal>
<Popover.Positioner
side="left"
anchor={
anchorRect
? { getBoundingClientRect: () => anchorRect }
: undefined
}
>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>
编辑 {rows.find((r) => r.id === selectedRow)?.name}
</Popover.Title>
<Popover.Description>
气泡卡片以选中的行为锚点,而不是图标按钮。
</Popover.Description>
<div className="mt-3">
<Popover.Close
render={<Button size="sm" variant="secondary" />}
>
关闭
</Popover.Close>
</div>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
</div>
);
}API 参考
Popover.Root
管理气泡卡片打开状态的根组件。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| side | "top" | "bottom" | "left" | "right" | "bottom" | 气泡卡片显示在触发元素的哪一侧。 - `"top"` — 触发元素上方 - `"bottom"` — 触发元素下方 - `"left"` — 触发元素左侧 - `"right"` — 触发元素右侧 |
Popover.Trigger
点击时打开气泡卡片的按钮。使用 render 可渲染你自己的元素。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Portal
在应用的 DOM 层级之外渲染气泡卡片。默认使用 LarkPortalProvider 提供的容器,也可以通过
container 属性覆盖。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Backdrop
渲染在气泡卡片后方的可选遮罩层。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Positioner
相对触发元素或 anchor 定位弹层。使用 side、align 及其偏移量属性来控制位置。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Popup
带样式的弹层容器。请将 Popover.Arrow 和气泡卡片内容直接放在其中。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Arrow
指向气泡卡片锚点的可选箭头。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Viewport
当同一个弹层由多个触发元素共享时,用于为内容切换添加动画的视口。普通气泡卡片或滚动内容 不需要它。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Content
已弃用的兼容包装组件,合并了 Portal、Positioner、Popup 和 Arrow。现有用法仍然
受支持,但新代码应组合上面展示的各个独立部分。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Title
为气泡卡片提供标签的标题,用于无障碍。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Description
为气泡卡片内容提供补充说明的段落。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
Popover.Close
点击时关闭气泡卡片的按钮。使用 render 可渲染你自己的元素。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。