焦点是键盘和屏幕阅读器用户在界面中移动的唯一坐标。本文从焦点顺序、焦点陷阱、模态管理、路由切换、组件组合与测试方法等角度,拆解焦点管理的设计逻辑,让无障碍不再停留在“加个 aria-label”。
一个只用鼠标的人,很少意识到焦点的存在。点击输入框,光标出现;点击按钮,页面响应。但对键盘用户和屏幕阅读器用户来说,焦点就是他们在界面中的唯一坐标。它决定“我现在在哪里”“我接下来能去哪里”“我刚才的操作有没有生效”。焦点丢失或顺序混乱时,视觉用户可能毫无察觉,键盘用户却会瞬间迷失:按了十几次 Tab 仍在原地打转,打开模态后焦点跑到背后的页面,关闭弹窗后不知道回到了哪里。焦点管理不是无障碍的附加项,而是组件交互的基础设施。
焦点到底是什么
焦点是操作系统和浏览器分配给某个可交互元素的“当前选中状态”。同一时刻,页面上通常只有一个元素拥有焦点。键盘用户通过 Tab、Shift+Tab、方向键、Enter、空格和 Esc 等按键与焦点元素交互;屏幕阅读器则跟随焦点朗读元素角色、名称、状态和提示。
焦点分为几种类型。浏览器原生可聚焦元素包括链接、按钮、输入框、下拉、复选框等,它们天然进入 Tab 顺序。tabindex="0" 可以让自定义元素加入顺序,tabindex="-1" 让元素可被脚本聚焦但不进入 Tab 顺序,大于 0 的值会打乱自然顺序,通常应避免。还有“焦点可见”问题:浏览器默认的 outline 常被设计师移除,如果不用自定义焦点环替代,键盘用户就完全看不到自己在哪。
焦点类型 | 行为 | 典型用途 |
|---|---|---|
原生可聚焦 | 自动进入 Tab 顺序 | 按钮、链接、表单控件 |
| 加入 Tab 顺序 | 自定义卡片、可点击区域 |
| 可脚本聚焦,不参与 Tab | 模态容器、跳转目标 |
焦点环 | 视觉指示 | 所有可聚焦元素 |
焦点陷阱 | 限制焦点范围 | 模态、抽屉、全屏浮层 |
焦点管理的目标,是让用户在任何时刻都能回答三个问题:我在哪,我能去哪,我怎么回去。
Tab 顺序不是小事
Tab 顺序由 DOM 顺序决定,而不是视觉位置。视觉上从左到右、从上到下的布局,如果 DOM 顺序混乱,键盘用户的移动路径就会跳跃。常见问题包括:用 CSS 把侧边栏放到右侧,但 DOM 里它在主内容之后;用绝对定位把“关闭”按钮放在右上角,但 DOM 里它在最后;用 flex 的 order 属性调整视觉顺序,却不动 DOM。
一个实用的检查方法是:把页面样式全部去掉,只按 Tab 走一遍,看顺序是否仍然符合阅读逻辑。如果去掉 CSS 后顺序混乱,屏幕阅读器用户也会遇到同样的问题。
Tab 顺序设计有几条基本原则:
主内容优先于辅助内容,除非辅助内容是当前任务。
一组相关操作应连续出现,不要被无关元素打断。
跳转链接应放在页面最前,让键盘用户跳过导航。
不要用
tabindex正值重新排序,应调整 DOM。隐藏元素不应可聚焦,用
display:none或hidden而非仅视觉隐藏。动态插入的内容要考虑是否打断当前焦点。
焦点可见:不要拿走用户唯一的坐标
设计师移除焦点环,通常是因为默认 outline 不好看。但焦点环是键盘用户唯一能看到的当前位置指示。移除它,等于让用户闭眼操作。
替代方案不是取消,而是重新设计。焦点环需要满足三个条件:可见、与背景对比、不被裁切。常见做法是用 :focus-visible 只在键盘操作时显示,鼠标点击不显示;用 box-shadow 或 outline 配合 outline-offset,让焦点环清晰且不挤压布局;在深色和浅色背景上都测试对比度,通常要求 3:1 以上。
按钮、链接、输入框、复选框、开关、卡片、菜单项,所有可聚焦元素都需要焦点样式。组内焦点还要区分“当前项”和“已选中项”,例如下拉选项滚动时,焦点在哪个选项上必须清晰可见。
模态与抽屉:焦点陷阱的正确做法
模态是焦点管理最容易出错的地方。打开模态后,如果焦点仍留在背后的页面,键盘用户按 Tab 会跑到被遮罩的内容上,屏幕阅读器也会继续朗读背后的信息。正确的做法是建立焦点陷阱:焦点进入模态,并在模态内部循环,直到模态关闭。
一个完整的模态焦点流程包括:
打开前记录触发元素。
打开后把焦点移到模态容器或第一个可聚焦元素。
限制 Tab 和 Shift+Tab 只在模态内循环。
支持 Esc 关闭。
关闭后把焦点还给触发元素。
如果触发元素已不存在,焦点移到逻辑上的替代位置。
焦点陷阱不能只靠 keydown 拦截 Tab 实现。更稳妥的方式是监听焦点离开模态容器时将其拉回,同时用 aria-modal="true" 和 role="dialog" 告知辅助技术。遮罩点击关闭、关闭按钮、取消按钮都要有明确焦点顺序。多级模态嵌套时,焦点应回到上一层模态,而不是页面。
抽屉、全屏菜单、命令面板、图片查看器,都属于同类场景。它们的共同点是:打开时用户进入一个临时上下文,关闭时必须回到原处。
路由切换与动态内容
单页应用的路由切换,对键盘和屏幕阅读器用户来说常常是“无声”的。视觉上页面变了,但焦点可能还停在导航链接上,屏幕阅读器也不会自动朗读新页面标题。用户按 Tab 后,可能从导航继续往后走,完全不知道内容已经更新。
路由切换后的焦点管理,通常有两种策略。一种是焦点移到主内容容器或页面标题,让用户从新页面开头开始。另一种是焦点留在导航,但用 aria-live 区域播报页面变化。前者更适合内容型页面,后者更适合频繁切换的标签式界面。
动态内容也需要考虑焦点。例如:
表单提交失败后,焦点移到第一个错误字段。
搜索无结果时,焦点移到结果区域或提示信息。
删除列表项后,焦点移到下一项或列表容器。
展开折叠面板后,焦点移到面板内容或保持触发按钮。
加载更多后,焦点不应被重置到页面顶部。
这些细节决定了键盘用户能否连续完成任务,而不是每次都被打断。
组件组合中的焦点传递
单个组件的焦点管理相对容易,组件组合时问题会放大。一个下拉菜单包含搜索框、选项列表、分组标题和“加载更多”;一个数据表格包含排序按钮、行复选框、行操作菜单和分页;一个日期选择器包含输入框、日历网格、月份切换和快捷选项。这些组合组件需要定义清晰的焦点模型。
组件类型 | 焦点进入 | 焦点移动 | 焦点离开 |
|---|---|---|---|
下拉菜单 | 触发按钮 | 方向键在选项间移动 | Esc 关闭并返回按钮 |
标签页 | 当前标签 | 方向键切换标签 | Tab 进入面板内容 |
数据表格 | 表格容器或首行 | 方向键在单元格间移动 | Tab 进入行内操作 |
日期选择器 | 输入框或日历 | 方向键在日期网格移动 | Esc 关闭并返回输入框 |
树形控件 | 根节点 | 方向键展开、折叠、移动 | Tab 离开树 |
组合框 | 输入框 | 方向键浏览建议 | Esc 关闭建议 |
组合组件常犯的错误,是把内部所有元素都放进 Tab 顺序,导致用户要按很多次 Tab 才能穿过一个组件。更好的做法是“一个组件一个 Tab 停留点”,内部用方向键导航。这被称为 roving tabindex 或 aria-activedescendant 模式。它让键盘操作更高效,也更符合用户对组件边界的预期。
如何测试焦点管理
焦点问题很难靠视觉检查发现,需要专门测试。最直接的方法是不碰鼠标,只用键盘完成核心任务。测试时关注:
Tab 顺序是否合理,是否跳过隐藏元素。
焦点环是否始终可见,是否被遮挡。
模态打开后焦点是否进入,关闭后是否返回。
路由切换后焦点是否到达合理位置。
动态内容更新后焦点是否丢失。
组合组件内部是否用方向键导航。
屏幕阅读器是否能朗读当前焦点元素。
是否存在键盘陷阱,用户能否离开任何组件。
屏幕阅读器测试可以配合 VoiceOver、NVDA 或 JAWS。打开朗读后,关闭显示器,尝试完成注册、搜索、下单等任务。如果无法完成,说明焦点路径存在断裂。
自动化工具可以检测部分问题,例如 axe、Lighthouse 的无障碍审计能发现焦点环缺失、aria 属性错误、对比度不足。但焦点顺序、焦点陷阱、动态焦点转移,仍然需要人工测试。把键盘测试纳入常规验收流程,比事后修复更有效。
焦点管理不是无障碍的“额外工作”
很多团队把焦点管理归入无障碍专项,等到合规审查才处理。但焦点问题影响的不只是残障用户。使用键盘快捷键的开发者、习惯 Tab 操作的高级用户、在移动设备上使用外接键盘的用户、临时手部受伤的用户,都会受益于清晰的焦点路径。焦点管理做好了,组件的键盘体验、屏幕阅读器体验和整体操作效率会一起提升。
更重要的是,焦点管理迫使团队重新思考组件的行为契约。一个下拉菜单不只是“点开、选一个、关闭”,它还要回答:打开时焦点去哪,选项间怎么移动,选中后焦点去哪,Esc 后回到哪。这些问题的答案,构成了组件交互的完整定义。把焦点写进组件规范,和定义悬停态、按下态、加载态一样重要。它不显眼,但它是键盘用户在界面中行走的隐形路径。路径清晰,用户才能到达目的地。
演示站内容均来自互联网,如有侵权,请与我联系
文章不错?点个赞呗~