大模型应用
设计弹窗与浮层时如何选择正确的覆盖层类型并正确实现焦点与无障…
在实现 modal、drawer、bottom sheet、popover 等覆盖层 UI 时,按任务阻塞程度选择最轻量的类型并正确处理焦点、ARIA 与破坏性确认。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
设计弹窗与浮层时如何选择正确的覆盖层类型通用
解决思路
- 按覆盖层层级选择最轻量的类型:Tooltip(不阻塞、锚定触发器、1–2 行说明文字、光标离开/失焦消失)、Popover(不阻塞、锚定、短交互内容、点击外部/Escape/显式关闭)、Dropdown/Menu(不阻塞、锚定、操作或选项列表、点击外部/Escape/选择关闭)、Bottom sheet(移动端、部分遮挡带遮罩、滑动下拉/点遮罩/Escape 关闭)、Drawer/Side panel(部分遮挡带遮罩、次要编辑/详情/长表单、Escape/显式关闭,可选点击遮罩)、Dialog/Modal(完全遮挡带全屏遮罩、阻塞任务、Escape(仅非破坏性)/显式按钮)、Full-screen overlay(完全遮挡、沉浸式任务、仅显式关闭)。
- 决策规则:如果用户打开覆盖层时仍能继续使用应用其余部分,用非阻塞类型(drawer、popover);如果应用必须等待用户响应,用 modal。
- Tooltip:内容为一句短语、标签或键盘快捷键,绝不放链接或按钮;hover 延迟 300–400ms,键盘聚焦无延迟;优先显示在触发器上方,接近视口边缘时自动翻转;元素加 role="tooltip",触发器用 aria-describedby 指向 tooltip id。
- Popover:最大宽度 280–360px,更大内容改用 drawer;锚定触发器并自动翻转保持在视口内;点击外部、Escape 或内容较长时用显式关闭按钮关闭;打开时把焦点移入 popover,关闭时返回触发器;交互内容用 role="dialog",列表用 role="listbox",触发器加 aria-haspopup。
- Dropdown/Menu:区分 select dropdown(用户选一个持久化的值)和 action menu(触发不持久化为值的操作);宽度至少与触发器相同,上限 280px;超过 8–10 项时在顶部加搜索输入;键盘 ↑/↓ 移动、Enter 选择、Escape 关闭。
- Bottom sheet(移动端):从小屏底部滑出,先显示 peek 高度(把手 + 标题)让用户拖动展开,长内容或表单用全高;滑动下拉、点遮罩或 Escape 关闭;移动端不要用居中 dialog,改用 bottom sheet;按 role="dialog" 处理并采用与 modal 相同的焦点管理。
- Drawer/Side panel:右侧 drawer 用于详情视图、编辑表单、筛选/排序面板(最常见),左侧 drawer 用于移动端导航(汉堡菜单);桌面宽度 320–480px,移动端全宽(实际改用 bottom sheet 或全屏覆盖层);用半透明遮罩 rgba(0,0,0,0.4) 变暗主内容;Escape 或显式关闭按钮关闭,点击遮罩可选——当 drawer 含未保存表单时避免点击遮罩关闭;任务阻塞时不要用 drawer,改用 modal;加 role="dialog"、aria-modal="true"、aria-labelledby 指向 drawer 标题。
- Modal/Dialog 仅在应用确实无法在用户响应前继续时使用:确认破坏性或不可逆操作、必须提交才能继续的必填表单、需要用户确认的错误或警告。
- 不要在以下情况用 modal:展示用户可随时阅读的信息(用内联通知或 notification)、用户可能想与主内容并行完成的任务(用 drawer)、字段很多的大表单(用独立页面或 drawer)。
- Modal 结构:Header(标题 + 关闭按钮)、Body(内容,必要时滚动)、Footer(操作按钮,右对齐)。
- Modal 尺寸:Small 360px 用于短确认和单字段提示,Medium 480px 用于标准对话框和短表单,Large 640px 用于多字段表单和较丰富内容,Full-screen 100% 视口用于沉浸式任务(少用);溢出内容只在 body 区域滚动,header 和 footer 必须保持可见。
- Modal 关闭行为:Escape 键对非破坏性允许、对破坏性不允许(要求显式 Cancel);点击外部遮罩对非破坏性允许(可选)、对破坏性不允许(太容易误关);关闭按钮 ✕ 和 Cancel 按钮两者都允许。
- 破坏性或不可逆操作:禁用 Escape 和点击外部关闭,用户必须显式按 Cancel 或 Confirm。
- 避免在 modal 内再开 modal;这通常意味着信息架构有问题、任务对单个对话框太复杂;如果次要覆盖层不可避免,在 modal 内用锚定的 popover 而不是另一个全 modal;绝不要堆叠两个阻塞性遮罩覆盖层。
- 焦点管理——打开时:把焦点移到覆盖层内第一个可交互元素(通常是第一个字段,确认框则是确认按钮);用 Tab 和 Shift+Tab 把焦点限制在覆盖层内循环,不能逃到遮罩后的内容。
- 焦点管理——关闭时:把焦点返回触发覆盖层的元素;如果触发器已不存在(元素被删除),把焦点移到逻辑上邻近的元素。
- 实现提示:使用焦点陷阱库或原生 <dialog> 元素(现代浏览器原生处理陷阱),手写焦点陷阱容易出错。
- Modal/Drawer 的 ARIA:容器加 role="dialog"、aria-modal="true"、aria-labelledby 指向对话框标题 id、aria-describedby 指向描述 id(可选但对确认框有帮助);遮罩背景加 aria-hidden="true",屏幕阅读器不得读取它。
- 破坏性确认模式:必须指明条目和后果,例如「Delete "Apollo Project"? This will permanently remove all tasks, files, and history. This cannot be undone. [Cancel] [Delete project]」,而不是泛泛的「Are you sure you want to do this? [No] [Yes]」。
- 破坏性主操作按钮使用 --color-error / --color-danger 而非 --color-primary;破坏性按钮要明确标注「Delete project」「Remove member」「Cancel order」,不要只写「OK」或「Confirm」;Cancel 始终在左(或次要位置),破坏性操作在右。
- 按评审清单逐项检查:是否为任务选了正确的覆盖层类型;是否在 drawer 或内联模式足够时避免了 modal;modal/drawer 是否有可见标题、正文和清晰操作按钮;溢出内容是否只在 body 滚动且 header/footer 固定;破坏性操作是否禁用 Escape(以及点击外部);打开时焦点是否移入覆盖层、关闭时是否返回触发器;打开期间焦点是否被限制在覆盖层内;role="dialog"、aria-modal="true"、aria-labelledby 是否正确设置;破坏性确认框是否指明条目并描述后果;破坏性按钮是否明确标注(不是 OK/Confirm);是否避免了堆叠 modal;移动端是否用 bottom sheet 替代 modal。
适用边界 · 注意事项
- 当用户可随时阅读信息、或任务可与主内容并行完成、或表单字段很多时,不要用 modal(改用内联通知、drawer 或独立页面)。
- 当任务本质是阻塞性的(例如必须做出的决定)时,不要用 drawer。
- 不要在 modal 内再开 modal 或堆叠两个阻塞性遮罩覆盖层;移动端不要用居中 dialog,改用 bottom sheet。