跳到主要内容

Sheet 底部弹层

从屏幕底边升起的模态卡片,带抓手,可以向下拖拽关闭。在手机上代替对话框和抽屉:离拇指近,也符合人在触屏上的习惯。

同一张悬浮卡片

Dialog、Drawer、Sheet、Panel 不是四套东西,而是同一张悬浮卡片(FloatingCard)在不同位置、不同尺寸下的用法:一张圆角卡片,和视口四周留一圈缝,从停靠的那一侧滑入。没有阴影,模态时靠遮罩压暗背景,非模态时靠描边和页面分开。遮罩、焦点圈、滚动锁、Esc、进退场都在 FloatingCard 里,只此一份;卡片里面统一用 FloatingHeader / FloatingBody / FloatingFooter 三个结构件来搭。

组件停靠位置模态用在哪
Dialog居中模态确认、简短的表单
Drawer贴着四边之一模态详情、较长的表单、导航
Sheet 本页贴着底边模态手机上代替对话框和抽屉
Panel贴着四边之一非模态检查器、筛选面板

结构件

Sheet 在卡片顶上自动加了一条抓手(FloatingGrip),其余和抽屉一样用 FloatingHeader / FloatingBody / FloatingFooter 来搭。

抓手与拖拽关闭

抓手和头部都是拖拽把手:按住往下拖,松手时按距离和速度决定关闭还是回弹。把手里的按钮、链接照常响应点按;内容区照常滚动。grip={false} 去掉抓手,dragToDismiss={false} 关掉拖拽。

高度

默认 auto,高度跟随内容。sm / md / lg / full 分别是视口高度的 40% / 60% / 85% / 100%(都减去间隙);也可以给数字(px)或 CSS 长度。内容超出时 FloatingBody 内部滚动。

宽屏上的宽度

窄屏上弹层总是横向撑满。宽屏上默认最宽 560 并居中,避免变成一条很扁的横幅;需要通栏时加 stretch。

规格

卡片
圆角 32 · 内边距 24 · 到视口边缘 12(窄屏 8)
抓手
36 × 5 的横杠,拖动时伸到 44
进场
自底边升起 · spring-soft 曲线 303ms,和所有悬浮卡片一致
拖拽关闭
超过高度的 40%(最多 160px),或速度超过 0.5px/ms
退场
160ms ease-in

接口

Sheet

属性类型默认值说明
size'auto' | 'sm' | 'md' | 'lg' | 'full' | number | string'auto'高度:auto 跟随内容;40% / 60% / 85% / 撑满;或数字(px)、CSS 长度。
gripbooleantrue顶上的抓手。
dragToDismissbooleantrue按住抓手或头部向下拖拽关闭。
stretchbooleanfalse宽屏上是否横向撑满;默认最宽 560,居中。
modalbooleantrue模态:带遮罩、锁滚动、圈住焦点。
role'dialog' | 'alertdialog' | 'complementary' | 'region''dialog'语义角色。
open / defaultOpenbooleanfalse开合,受控 / 非受控。
onOpenChange(open: boolean) => void—开合变化时(Esc、点遮罩、关闭按钮、拖拽关闭都会触发)。
dismissiblebooleantrue允许 Esc 和点遮罩关闭。
gapnumber12(窄屏 8)卡片到视口边缘的间隙(px)。
initialFocusRefObject<HTMLElement>—模态时打开后焦点落在哪。默认是卡片里带 data-autofocus 的元素,没有就落在卡片本身。
restoreFocusbooleantrue关闭后把焦点还给打开前的元素。
containerHTMLElement | null—渲染到哪个容器,默认 body。
onExited() => void—退场动画结束、节点卸载后。
aria-label / aria-labelledby / aria-describedbystring—不传时自动接上 FloatingHeader 的标题和说明。

FloatingHeader

属性类型默认值说明
titleReactNode—标题。自动成为卡片的 aria-labelledby。
descriptionReactNode—标题下面的一行说明。自动成为卡片的 aria-describedby。
iconReactNode—标题左边的图标位(40×40 的色块)。
level1 | 2 | 3 | 4 | 5 | 62标题的层级。
closableboolean—是否带关闭按钮。默认跟随卡片的 dismissible。
closeLabelstring'关闭'关闭按钮的读屏名称。
onClose() => void—点关闭按钮时;不传则请求卡片关闭。
childrenReactNode—放在关闭按钮前面的额外操作。

FloatingBody / FloatingFooter

属性类型默认值说明
paddedbooleantrue是否带内边距。内容自己通栏(列表、表格)时关掉。
align'start' | 'end' | 'between' | 'stretch''end'按钮的排法:靠左、靠右、两端、等分撑满。

无障碍

  • role="dialog" 加 aria-modal="true",名字和描述取自 FloatingHeader。焦点移入并被圈住,关闭后还给触发元素。
  • 抓手是纯装饰(aria-hidden),拖拽只是指针上的捷径。键盘和读屏用户用 Esc 或关闭按钮;所以去掉头部的关闭按钮时,要确保卡片里还有别的按钮能关。
  • 把手区域设了 touch-action: none,手指在上面拖动不会带着页面滚;内容区的原生滚动不受影响。
  • 打开期间页面背景锁定滚动。减弱动效时升起和落下直接完成,拖拽仍然可用。