焦点是键盘和屏幕阅读器用户在界面中移动的唯一坐标。本文从焦点顺序、焦点陷阱、模态管理、路由切换、组件组合与测试方法等角度,拆解焦点管理的设计逻辑,让无障碍不再停留在“加个 aria-label”。

一个只用鼠标的人,很少意识到焦点的存在。点击输入框,光标出现;点击按钮,页面响应。但对键盘用户和屏幕阅读器用户来说,焦点就是他们在界面中的唯一坐标。它决定“我现在在哪里”“我接下来能去哪里”“我刚才的操作有没有生效”。焦点丢失或顺序混乱时,视觉用户可能毫无察觉,键盘用户却会瞬间迷失:按了十几次 Tab 仍在原地打转,打开模态后焦点跑到背后的页面,关闭弹窗后不知道回到了哪里。焦点管理不是无障碍的附加项,而是组件交互的基础设施。

焦点到底是什么

焦点是操作系统和浏览器分配给某个可交互元素的“当前选中状态”。同一时刻,页面上通常只有一个元素拥有焦点。键盘用户通过 Tab、Shift+Tab、方向键、Enter、空格和 Esc 等按键与焦点元素交互;屏幕阅读器则跟随焦点朗读元素角色、名称、状态和提示。

焦点分为几种类型。浏览器原生可聚焦元素包括链接、按钮、输入框、下拉、复选框等,它们天然进入 Tab 顺序。tabindex="0" 可以让自定义元素加入顺序,tabindex="-1" 让元素可被脚本聚焦但不进入 Tab 顺序,大于 0 的值会打乱自然顺序,通常应避免。还有“焦点可见”问题:浏览器默认的 outline 常被设计师移除,如果不用自定义焦点环替代,键盘用户就完全看不到自己在哪。

焦点类型

行为

典型用途

原生可聚焦

自动进入 Tab 顺序

按钮、链接、表单控件

tabindex="0"

加入 Tab 顺序

自定义卡片、可点击区域

tabindex="-1"

可脚本聚焦,不参与 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 会跑到被遮罩的内容上,屏幕阅读器也会继续朗读背后的信息。正确的做法是建立焦点陷阱:焦点进入模态,并在模态内部循环,直到模态关闭。

一个完整的模态焦点流程包括:

  1. 打开前记录触发元素。

  2. 打开后把焦点移到模态容器或第一个可聚焦元素。

  3. 限制 Tab 和 Shift+Tab 只在模态内循环。

  4. 支持 Esc 关闭。

  5. 关闭后把焦点还给触发元素。

  6. 如果触发元素已不存在,焦点移到逻辑上的替代位置。

焦点陷阱不能只靠 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 后回到哪。这些问题的答案,构成了组件交互的完整定义。把焦点写进组件规范,和定义悬停态、按下态、加载态一样重要。它不显眼,但它是键盘用户在界面中行走的隐形路径。路径清晰,用户才能到达目的地。

© 版权声明
演示站内容均来自互联网,如有侵权,请与我联系

文章不错?点个赞呗~