把模态弹窗写成从打开到焦点返回的完整流程
本文目录
模态弹窗请求应说明:打开对话框时键盘焦点去哪里、用户怎样在内部移动,以及关闭后返回哪里。还要覆盖原触发控件已经消失的情况。一张遮罩层截图,无法单独回答这些问题。
下面的虚构 Rowan Board 示例是一个只读条目详情对话框。它不删除条目、不提交表单,也不打开另一个对话框。这份完整交互约定可以直接交给 Agent,无需先选 UI 框架。
先画出对象,再描述焦点移动
Rowan Board 最初显示两个条目:第一个是 C7,标题为“Inspect build output”;第二个是 C8,标题为“Check release wording”。每项都有 Details 按钮。打开 C7 后,即使周围列表发生变化,也必须继续显示 C7 的详情。
页面
└─ board-region
├─ board-title: "Review board"(稳定标题;可通过程序获得焦点)
├─ item-C7: "Inspect build output"
│ └─ details-C7: "Details for Inspect build output" 按钮
└─ item-C8: "Check release wording"
└─ details-C8: "Details for Check release wording" 按钮
独立模态层,位于背景区域之外
└─ item-dialog: 由 details-title 命名的 dialog
├─ details-title: "Inspect build output — details"(可获得焦点的标题)
├─ details-body: 下方两段正文
├─ show-files: "Show file list" 切换按钮
├─ file-list: 初始隐藏的静态文字,由 show-files 控制
└─ close-details: "Close details" 按钮
完整虚构内容如下,保留英文示例正文并附中文释义:
This item collects the build output that a reviewer needs before reading the proposed release wording. Keep the original output available beside any summary. 中文释义:此条目汇集复核者在阅读拟议发布措辞前所需的构建输出。无论是否有摘要,都应把原始输出保留在旁边,供人查阅。
The next step is to inspect the listed files and record a review outcome in the board. Opening these details does not mark the item reviewed. 中文释义:下一步是检查列出的文件,并在看板中记录复核结果。打开这些详情,不会把条目标记为已复核。
文件列表包含 build-summary.txt 和 release-notes-draft.md。这些是示例名称,只显示为文字,不打开或下载。切换按钮只改变列表是否可见。在这次打开期间,对话框标题、正文和身份始终属于 C7。
选择初始阅读位置
W3C 的模态对话框模式介绍了焦点进入对话框、留在其 Tab 序列内,并在关闭后回到合适位置的行为。它也讨论了当内容适合结构化阅读时,先让一个静态起始元素获得焦点的做法。
在 Rowan Board 中,选择 details-title 作为初始焦点目标,并设置 tabindex="-1"。这样,阅读起点位于两段正文之前。标题可以通过程序获得焦点,但不会在普通 Tab 序列里多占一个停靠点。
两个普通 Tab 停靠点依次是 show-files 和 close-details。对话框打开期间,两者始终存在且可用。展开文件列表不引入链接或额外控件,因此不会悄悄改变这个序列。
这些是为本对话框作出的选择。用途不同的简短确认框,可能需要另一个初始目标。
给每次键盘移动指定去向
使用下面这份完整移动表,而不是只要求“把焦点困在弹窗内”。
| 起始位置 | 操作 | 去向或结果 |
|---|---|---|
details-C7 |
激活 Details | 打开 C7 对话框;焦点到 details-title |
details-title |
Tab | 焦点到 show-files |
details-title |
Shift+Tab | 焦点到 close-details |
show-files |
Tab | 焦点到 close-details |
close-details |
Tab | 循环回 show-files |
close-details |
Shift+Tab | 焦点到 show-files |
show-files |
Shift+Tab | 循环回 close-details |
show-files |
激活 | 切换文件列表;焦点留在 show-files |
| 对话框内任一当前焦点 | Escape | 关闭一次,再应用下方返回规则 |
close-details |
激活 | 关闭一次,应用相同返回规则 |
对话框处于活动状态时,看板不可接受指针或键盘交互。本设计中,点击背景遮罩不关闭对话框;关闭路径是可见的关闭按钮和 Escape。背景变暗用来表达模态状态,实际的交互处理负责落实限制。
对话框的无障碍名称与可见标题关联。控件都位于对话框容器内。两段描述正文保留各自结构;这份方案不把它们压成一段很长的容器描述。ARIA 命名和模态属性描述预期语义,实施仍需提供真正的焦点和背景交互行为。
用一个简明决定定义返回目标
正常关闭时,如果打开本次对话框的那个 Details 按钮仍存在且可用,就把焦点还给它。不要查找“第一个 Details 按钮”,因为那时它可能已经属于 C8。
| 关闭时的状态 | 返回目标 | 为什么选择它 |
|---|---|---|
原 details-C7 仍然可见且启用 |
details-C7 |
用户可从刚才打开的条目继续 |
| 原触发控件被移除、隐藏或禁用 | board-title |
稳定标题可以标识所在工作区域 |
先恢复背景交互,再把焦点移回背景。如果实施过程中临时改变了可获得焦点的状态或背景状态,应恢复它们之前的状态,而不是把页面上的所有内容一律启用。关闭本身不应改变哪些看板条目已经被复核。
本约定假设看板页面和 board-title 仍然挂载。销毁整个页面的导航会结束这段对话框流程;路由和下一页的焦点规则属于另一项工作。
走一遍触发控件消失的情况
以下历史是虚构的。它故意通过本地数据更新测试材料,在对话框仍打开时移除 C7。用户并不是通过操作不可交互的背景来造成这次变化。
- C7 和 C8 可见。用户通过
details-C7打开 C7。 - 焦点移到
details-title;对话框仍描述 C7。 - 用户按 Tab 到
show-files并激活它。两个文件名出现,焦点仍在该按钮。 - 一次预先设定的本地数据模型更新将 C7 标为已复核,当前“未复核”筛选因此从背景中移除该行。C8 和
board-title仍保留。这份教学测试材料不涉及服务器事件或真实账户。 - 用户按 Tab 到
close-details并激活它。 - 对话框关闭,背景交互恢复,焦点移到
board-title。它不跳到 C8 的 Details 按钮,也不留在已经移除的节点上。 - 用户下次打开某个条目的详情时,文件列表重新从折叠状态开始。之前的展开状态不泄漏到新一次打开中。
W3C 的焦点返回指引允许在调用元素已不存在时,选择合乎工作流逻辑的目标。本例选择看板标题,把这个原则具体化;它不要求每个应用都把焦点还给标题。
复制这份有范围的请求
为虚构 Rowan Board 实现一个只读条目详情模态框。
使用给定对象地图、C7/C8 内容和移动表。
不要增加嵌套对话框、编辑、复核提交或文件下载。
首个人工检查目标:macOS 26 上的 Safari 26,启用键盘导航;
同时使用该目标的 VoiceOver 设置检查对话框。
这些是选定目标,不是作者电脑的环境清单。
报告结果前,记录精确构建版本和设置。
打开时,用可见标题为对话框命名,填入选定条目的内容,
折叠其静态文件列表,并让标题获得焦点(tabindex=-1)。
两个 Tab 停靠点依次是 Show file list、Close details。
实现表中每个 Tab 和 Shift+Tab 转移,包括初始标题、
第一个控件和最后一个控件的边界。
整个模态生命周期内,背景内容不可接受键盘或指针交互。
Show files 只切换静态列表,报告展开状态,并保留焦点。
点击背景遮罩不关闭。Escape 和 Close details 使用同一套、只关闭一次的过程。
恢复背景交互后,如果保存的原触发控件仍连接在文档中、可见且启用,
则返回它。否则,让仍挂载的 board-title 标题获得焦点。
不要改用另一行的按钮。只恢复本模态框实际改变的背景状态。
走查正常返回,以及完整的 C7 移除历史。
在本次打开期间保留该条目的内容快照。
不要从这项修改推断全站无障碍达标或认证。
返回实施结果,并把实际结果放在预期流程旁边。
这是一份完整交互提案,不是测试过的 HTML 组件。实施者可以采用合适的原生平台行为或已有应用设施,但可观察的焦点去向应保持一致。
分开走查循环与退出
下表的实际观察栏有意留空;作者尚未在浏览器或辅助技术会话中运行这个对话框。
| 走查项 | 预期结果 | 实际观察 | 状态 |
|---|---|---|---|
| 打开 C7 | C7 标题获得焦点;名称对应正确内容 | NOT_RUN | |
| 从标题按 Tab | Show file list 获得焦点 | NOT_RUN | |
| 从标题按 Shift+Tab | Close details 获得焦点 | NOT_RUN | |
| 在两个按钮之间正向循环 | 最后一个回到第一个;背景始终不获得焦点 | NOT_RUN | |
| 在两个按钮之间反向循环 | 第一个回到最后一个 | NOT_RUN | |
| 切换文件列表 | 可见性和展开状态正确;焦点保留 | NOT_RUN | |
| 尝试背景交互 | 模态打开时,看板不被激活 | NOT_RUN | |
| 原触发控件存在时按 Escape | 关闭一次;回到同一触发控件 | NOT_RUN | |
| 原触发控件存在时用关闭按钮 | 与 Escape 相同的返回目标 | NOT_RUN | |
| 打开期间 C7 被移除 | 内容仍是 C7;关闭后回到看板标题 | NOT_RUN | |
| 再次打开 | 显示正确的新条目;文件列表初始折叠 | NOT_RUN |
即使对话框看起来正确,返回目标放错也会让流程显得中断。先写清退出去向,Agent 和复核者就都有一个明确的任务终点。