mcpField
M
modal-and-overlay-patterns

modal-and-overlay-patterns

github.com/dembrandt设计与 UI174707 次安装1 周前更新
代码仓库

关于 modal-and-overlay-patterns

为界面设计提供覆盖层模式指南,帮助设计师和前端开发者按任务选择提示框、抽屉或模态框,并统一交互与无障碍规则。

主要功能

  1. 01按阻断程度选择覆盖层
  2. 02规范提示框与弹出层
  3. 03区分下拉选项和操作菜单
  4. 04制定移动端底部面板规则
  5. 05说明模态框关闭与堆叠行为
  6. 06覆盖媒体查看器缩放交互

适用场景

  1. 01设计确认删除等阻断流程
  2. 02为移动界面选择导航面板
  3. 03统一产品中的弹出界面样式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dembrandt/dembrandt-skills --skill modal-and-overlay-patterns

安装后在 Claude Code 中输入/modal-and-overlay-patterns确认已加载。

详细介绍

模态框与叠加层模式

叠加层显示在主内容层之上。它们涵盖从轻量级弹出提示(非阻塞,锚定到触发元素)到完全阻塞的模态框(应用继续运行前需要用户响应)等不同类型。为任务选择合适的叠加层类型,可以避免不必要的打断,并帮助用户保持方向感。


叠加层层级

选择满足任务需求的最轻量级类型。越重的叠加层,认知成本越高。

类型阻挡背景锚定到触发元素典型内容关闭方式
工具提示否是1–2 行说明文字光标移开/失去焦点
弹出面板否是简短的交互内容:表单字段、选择器、小型列表点击外部区域、按 Escape、显式关闭
下拉菜单/菜单否是操作或选项列表点击外部区域、按 Escape、选择项目
底部面板(移动端)部分(背景变暗)否小屏幕上的操作或内容向下滑动、点击遮罩、按 Escape
抽屉/侧边面板部分(背景变暗)否次要编辑、详情视图、长表单按 Escape、显式关闭;也可点击遮罩
对话框/模态框是(完整遮罩)否阻塞式任务:确认操作、填写必填表单按 Escape(仅限非破坏性操作)、点击显式按钮
全屏叠加层是(完全阻挡)否沉浸式任务:媒体查看器、复杂配置仅显式关闭

决策规则: 如果用户可以在叠加层打开时继续使用应用的其他部分,请使用非阻塞类型(抽屉、弹出面板)。如果应用必须等待用户响应,请使用模态框。


整个组件系列共用一个外壳

工具提示、弹出面板、抽屉、底部面板和对话框属于同一个组件系列,而不是五个独立组件。它们共用一个外壳:相同的凸起表面、一道极细边框、同一种圆角,以及随模态程度增强的阴影。带有页眉和页脚的组件还共用内部结构:用于标明对象的标题、页眉下方的一道极细分隔线,以及统一的页脚顺序。它们之间只有尺寸、锚定方式(箭头指向或居中)和阻塞性不同。

如果每种叠加层各自设置样式,用户面对的就是同一类信息的不同表达方式:圆角、按钮顺序和确认标签各不相同。每一种差异都需要用户花时间重新熟悉,也更容易让人误读破坏性操作的确认信息。统一外壳也意味着共用一个代码基础组件和一个设计工具组件系列,因此修复可以应用到所有地方。

添加前先审查。 列出产品中的每个浮动界面及其使用的设计令牌。将外壳提取为一个基础组件,并以此构建各类叠加层。通常会发现,确认弹出面板和对话框的页脚顺序不一致,或者对话框页眉仍保留着其他界面早已弃用的粗分隔线。


工具提示

工具提示在悬停或键盘聚焦时出现,并在触发元素失去焦点时消失。它只用于提供信息,内部不能包含交互元素。

  • 内容:一句简短的话、标签或键盘快捷键。切勿在工具提示内放置链接或按钮。
  • 延迟:悬停时延迟 300–400ms;键盘聚焦时不延迟。
  • 位置:优先显示在触发元素上方;接近视口边缘时自动翻转方向。
  • ARIA:在元素上设置 role="tooltip";在触发元素上设置 aria-describedby,指向工具提示的 id。

弹出面板

弹出面板锚定到触发元素,但包含交互内容,例如颜色选择器、日期范围选择器、小型表单或筛选条件列表。与工具提示不同,用户交互时它会保持打开状态。

  • 最大宽度:280–360px。内容较大时,请使用抽屉。
  • 位置:锚定到触发元素;自动翻转方向以保持在视口内。
  • 关闭方式:点击外部区域、按 Escape,或在内容较长时使用显式关闭按钮。
  • 焦点:打开时将焦点移入弹出面板;关闭时将焦点还给触发元素。
  • ARIA:如果包含交互内容,使用 role="dialog";如果是列表,使用 role="listbox";在触发元素上设置 aria-haspopup。

下拉菜单和菜单

下拉菜单会显示可选择的选项或操作,并锚定到触发按钮。它是最轻量的交互式浮层。

  • 区分选择下拉菜单(用户选择一个会持续保留的值)和操作菜单(用户触发一项不会作为值持续保留的操作)。
  • 宽度:至少与触发按钮等宽;最大不超过 280px。
  • 列表较长时:超过 8–10 项时,在顶部添加搜索输入框。
  • 键盘操作:按 ↑/↓ 在项目间移动,按 Enter 选择,按 Escape 关闭。

底部弹出层(移动端)

在小屏幕上,底部弹出层会取代模态框和弹出层。它从底边向上滑入,触控设备上的使用体验更自然。

  • **预览高度:**先显示弹出层的一小部分(拉手和标题),让用户拖动以展开。
  • **全屏高度:**适用于较长内容或需要占满视口的表单。
  • 关闭方式:向下滑动、点击遮罩层或按 Escape。
  • 不要在移动端将对话框置于屏幕中央——改用底部弹出层(居中的模态框尺寸过小且难以触及)。
  • ARIA:设为 role="dialog",并采用与模态框相同的焦点管理方式。

抽屉 / 侧边面板

抽屉从左侧或右侧滑入,部分遮挡主内容。适用于次要编辑任务、详情视图或设置等用户使用主内容时可能需要参考的内容。

  • **右侧抽屉:**详情视图、编辑表单、筛选/排序面板。最常见。
  • **左侧抽屉:**移动端导航(汉堡菜单模式)。
  • 宽度:桌面端为 320–480px。移动端为全宽(实际上会变成底部弹出层或全屏覆盖层)。
  • 遮罩层:抽屉后方的半透明背景(rgba(0,0,0,0.4))会使主内容变暗。
  • 关闭方式:按 Escape 键,或点击明确的关闭按钮。点击遮罩层关闭为可选操作——抽屉包含未保存的表单时应避免这种方式。
  • 如果任务会阻止用户继续操作(例如必须做出决定),不要使用抽屉。应改用模态框。
  • ARIA:设置 role="dialog"、aria-modal="true",并将 aria-labelledby 指向抽屉标题。

模态框 / 对话框

模态框通过全屏遮罩层阻止用户操作整个界面。仅当应用确实无法在用户响应前继续时才使用。

何时使用模态框
  • 确认破坏性或不可逆的操作
  • 必须在继续前提交的必填表单
  • 需要用户确认的错误或警告
何时不使用模态框
  • 展示用户可以稍后阅读的信息 → 使用行内提示或通知
  • 用户可能想在操作主内容的同时完成的任务 → 使用抽屉
  • 包含许多字段的大型表单 → 使用专用页面或抽屉
关闭标签页的情况

beforeunload 会显示浏览器自带的通用对话框,仅此而已。该对话框出现至页面卸载之间不会运行任何脚本,也不会渲染自定义标记。不要围绕此对话框设计“离开前评分”或“检查更改”流程。应针对应用可控制的操作(关闭按钮、路由切换、新搜索)设置拦截,并将关闭标签页视为退出途径。

结构
┌──────────────────────────────────┐
│  Title                      [✕]  │  ← Header: title + close button
├──────────────────────────────────┤
│                                  │
│  Body content                    │  ← Content: scrolls if needed
│  (description, form, media)      │
│                                  │
├──────────────────────────────────┤
│               [Cancel]  [Confirm]│  ← Footer: actions, right-aligned
└──────────────────────────────────┘
尺寸
尺寸宽度适用场景
小360px简短确认、单字段提示词
中480px标准对话框、简短表单
大640px多字段表单、内容较丰富的场景
全屏100% viewport沉浸式任务;谨慎使用

超出模态框高度的内容应仅在 正文区域 内滚动,标题栏和页脚必须保持可见。

移动优先的对话框应有专属的桌面布局。 在大屏幕上居中的手机宽度卡片会显得局促、不完整:选项换行后挤在狭窄的单列中,交互目标之间的距离也小于所需间距。为桌面断点设置专属宽度、列数和间距,同时在移动端保留原有布局。

每个选项有两三个子选项时,应将其内联显示。 如果选择器中的每个选项都只有少数几个二级选项,就将它们作为始终可见的链接显示在该选项下方。使用折叠面板或另一个对话框会多出一次点击,还会隐藏可选内容;而单独设置第二个维度的选择器,则会让用户选到实际上不存在的组合。

关闭行为
触发方式是否允许用于非破坏性操作?是否允许用于破坏性操作?
Escape 键是否——必须明确选择取消
点击遮罩层外部是(可选)否——太容易误关闭
关闭按钮(✕)是是
取消按钮是是

对于破坏性或不可逆操作:禁用 Escape 键和点击外部关闭。用户必须明确按下取消或确认。

模态框叠加

避免在模态框中再打开模态框。这表明信息架构存在问题——这项任务可能过于复杂,无法用单个对话框完成。

如果不可避免地需要第二层浮层,请使用锚定在模态框内的弹出层,而不是另一个完整模态框。绝不要叠加两个会阻止交互的遮罩层。


灯箱 / 媒体查看器

灯箱可能没有可见的标题和正文,但仍有交互控件(关闭、翻页和缩放),因此必须是一个带有可访问名称的对话框。关于遮罩层、关闭方式、焦点和文本替代内容的规则仍然适用;缩放和翻页就是全部交互,需要像设计表单一样认真设计。

打开时定位到被点击的项目。 每个缩略图都应让查看器打开到对应的索引,而不是第一张图片。按 Escape 键关闭;打开时将焦点放在关闭按钮上,关闭后将焦点返回缩略图。

根据指针类型选择两种输入模式,而不是根据屏幕宽度。 在运行时通过 matchMedia('(pointer: fine)') 选择默认模式,因为带触摸屏的笔记本电脑和配有触控板的平板电脑都存在。它只会报告主指针,因此应让两种模式同时可用,并根据每个事件的 pointerType 做出判断。

  • 精细指针:点击时在指针所在位置切换缩放。可通过移动指针平移(速度快,但用户伸手去点关闭按钮时,图片会随之移动),也可通过拖动平移(操作更从容,但需要多学一个手势)。选定一种后,在整个产品中保持一致。
  • 粗略指针:双指捏合缩放、双击切换缩放、拖动平移。这些都是平台教会用户的操作。

不能只靠手势。 显示 + / − 按钮,提供易于发现且可通过键盘操作的缩放方式。当缩放比例为 1× 时禁用 −,达到最大比例时禁用 +,这样按钮也能告知用户当前所处的位置。将 + / − 键和方向键绑定为平移操作,也是对同一规则的合理扩展。

两级缩放。 点击或双击时,在用户触碰的位置跳转到固定的细节级别。按钮则可继续逐级放大,直至硬性上限。具体级别取决于素材:例如,产品摄影可采用 2× 和 4×,地图、扫描件或复杂图表则可以更高。一次直接跳到最大比例,会让用户看到一片模糊像素,却不知道自己身处何处。

光标应提示下一步操作。 在 1× 时显示放大光标,放大后显示缩小光标,在打开查看器的缩略图上显示展开光标。如果缩放实现会在放大后的图片上设置自己的 cursor: move,请覆盖它,否则提示会在交互过程中改变。

区分拖动和平移与点击。 按下后到松开前,如果移动超过几个像素(约 5 px),则应视为平移;松开时不得切换缩放,否则每次平移结束都会让图片跳回 1×。

缩放只对单张图片生效。 用户切换到下一张图片时重置为 1×;图片恢复到 1× 后,应允许滑动再次操作轮播。

固定覆盖层工具的位置。 关闭按钮放在右上角,翻页控件放在底部中央,缩放控件放在右下角。用户只需熟悉一次;如果工具在不同屏幕上移动,这种熟悉就会白费。

播报状态。 每个图标按钮都必须带有 aria-label。除此之外,无障碍的基本要求与任何对话框相同:向屏幕阅读器提供当前缩放比例,可将其写入按钮标签(“放大,当前为 2×”),或使用礼貌播报的实时区域;缩放过渡还应尊重 prefers-reduced-motion。参见 [[wcag-accessibility]]。


焦点管理

每个覆盖层都必须正确管理焦点。焦点管理出错是最常见的无障碍问题之一。

打开时
  1. 将焦点移到覆盖层内第一个可交互元素上(通常是第一个字段;确认对话框通常是确认按钮)。
  2. 限制焦点范围:Tab 和 Shift+Tab 只能在覆盖层内循环;焦点不能移到遮罩层后面的内容上。
关闭时

将焦点返回到触发覆盖层的元素。如果触发元素已不存在(该元素已被删除),则将焦点移到附近逻辑上合适的元素。

实现说明

使用焦点陷阱库,或使用原生 <dialog> 元素;在现代浏览器中,它会原生处理焦点限制。手动实现焦点陷阱容易出错。


模态框和抽屉的 ARIA

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
>
  <h2 id="modal-title">Delete project?</h2>
  <p id="modal-description">
    This will permanently delete "Apollo" and all its data. This cannot be undone.
  </p>
  <button>Cancel</button>
  <button>Delete</button>
</div>
  • 在容器上设置 role="dialog"
  • aria-modal="true" 用于告知屏幕阅读器忽略遮罩层后方的内容
  • aria-labelledby 指向对话框标题的 id
  • aria-describedby 指向描述的 id(可选,但有助于确认操作)
  • 背景遮罩应设置 aria-hidden="true"——屏幕阅读器不得读取它

破坏性操作确认模式

破坏性操作的确认对话框必须指出操作对象和后果。泛泛地询问“确定吗?”无法提供足够的上下文。

应该这样做:

删除“阿波罗项目”? 这将永久删除所有任务、文件和历史记录,且无法撤销。 [取消] [删除项目]

不要这样做:

确定要执行此操作吗? [否] [是]

  • 主要破坏性操作按钮应使用 --color-error / --color-danger,而不是 --color-primary。
  • 明确标注破坏性操作按钮:“删除项目”“移除成员”“取消订单”,而不只是“确定”或“确认”。
  • 取消按钮始终位于左侧(或次要位置);破坏性操作按钮位于右侧。

紧邻单元格上的工具提示

紧邻组中某个单元格的工具提示应在整个组之外打开。当单元格在网格、树图或分段标记中紧邻排列时,应将每个单元格的工具提示放在整个组的上方或旁边,绝不要覆盖相邻单元格:锚定在底行单元格上的工具提示会遮住读者即将指向的那一行。应让工具提示忽略指针,以便移至相邻单元格时能打开下一个工具提示。


审查清单

  • 是否为当前任务选择了正确的覆盖层类型(工具提示 / 弹出框 / 菜单 / 底部面板 / 抽屉 / 模态框)?
  • 是否避免使用模态框,改用抽屉或行内模式就足够?
  • 模态框/抽屉是否有清晰可见的标题、正文和操作按钮?
  • 溢出内容是否在正文区域内滚动,同时固定页眉和页脚?
  • 对于破坏性操作,是否禁用了 Escape 键(点击外部区域关闭也应禁用)?
  • 打开时焦点是否移入覆盖层,关闭时是否返回触发元素?
  • 覆盖层打开时,焦点是否被限制在其中?
  • role="dialog"、aria-modal="true"、aria-labelledby 是否设置正确?
  • 破坏性操作确认对话框是否指出操作对象并说明后果?
  • 破坏性操作按钮是否有明确标签(而不是“确定”或“确认”)?
  • 是否避免了叠加模态框?
  • 是否没有围绕 beforeunload 设计自定义 UI?
  • 在移动设备上,是否将模态框替换为底部面板?
  • 灯箱:点击/平移交互是否根据指针类型而定,且 +/− 按钮和按键是否与手势一起提供?
  • 灯箱:光标是否显示放大/缩小,且平移操作是否不会在释放时切换缩放?
  • 灯箱:页面更改时缩放是否重置,当前缩放比例是否提供给屏幕阅读器?
  • 紧邻单元格上的工具提示是否在组外打开,并允许指针移至相邻单元格?
  • 所有覆盖层是否共用同一套外壳(表面、边框、圆角、阴影比例)和同一页脚顺序?

评论

支持 Markdown