通知标签页开着,面板就在屏幕上:“选择你希望接收的邮件和推送提醒。”VoiceOver 落到另一个标签上,念出:Account,已选中,标签,1/2,提醒偏好,标签组。而停在通知标签上时,它念的是:通知,标签,2/2。没有“已选中”。你看到的标签,不是代码认定的标签。
WCAG 4.1.2 名称、角色、值包含三部分。此前拆过自定义 div 冒充复选框的案例,问题出在角色——控件从没告诉 VoiceOver 自己是个复选框。这次的问题出在值:名称和角色都对(Account,标签,1/2),错的是状态。
两个案例都来自真实的无障碍树与 VoiceOver 录制。案例一,role="tab" 却完全没有 aria-selected;案例二,aria-selected 被写死成字面值。选中状态藏在 CSS 类名里,辅助技术读不懂类名。
案例一:状态根本没暴露
标签列表本身是真的:role="tablist" 带 aria-label,两个按钮带 role="tab"。当前标签只是换了个背景类,标记里没有任何地方说明哪个被选中。
首次加载、Account 视觉上处于激活态时,VoiceOver 念的是“Account,已选中,标签,1/2”。听起来没问题,但标记里并没有 aria-selected。录制记录特别注明:VoiceOver 可能在没有对应程序化值的情况下念出“已选中”,不能拿它当依据。
激活通知、通知面板可见之后,它念:“通知,标签,2/2。你当前位于一个标签上,2/2。要选择此选项,请按 Control-Option-Space。”它在要求你选择一个你刚刚已经选中的标签。
无障碍树里的记录更直接:
- tablist "Account settings",multiselectable: false
- tab "Account" — focusable: true,selected: false
- tab "Notifications" — focusable: true,selected: false
两个标签,一个都没选中。屏幕上一个面板开着,代码里一个选中状态都没有。缺少 aria-selected,树里的选中状态就不会跟随视觉上的激活标签。
案例二:状态写死了
这是半吊子修复。有人读到“要加 aria-selected”,就加了——加成了字面值。点击会更新 case2Active,它切换样式和面板,但 aria-selected 从不读这个状态。首次加载时看起来甚至是对的,因为 Account 本来就是起始标签。只有切换之后,bug 才露出来。
通知获得焦点、面板可见时,VoiceOver 念:“通知,标签,2/2。”而已经不再是打开标签的 Account,念的是:“Account,已选中,标签,1/2,提醒偏好,标签组。”
树里同样如此:Account 是 selected: true,通知是 focused: true、selected: false。树和 VoiceOver 意见一致,两者都和屏幕不一致。焦点移动了,选中状态没动。
为什么这过不了考试式的检查:4.1.2(A 级)要求用户界面组件的名称和角色可被程序化确定,用户可设置的状态可被程序化设置,且这些项的变化对辅助技术可见。哪个标签被选中,是用户点击标签设置的状态。代码不更新它,辅助技术拿到的就是错的信息。
WAS 规则对自定义组件的要求写得很清楚:aria-expanded、aria-selected、aria-checked、aria-pressed 这类状态必须在界面变化时更新,过期值会误导屏幕阅读器用户。案例一暴露的是“没有选中标签”,案例二暴露的是“选错了标签”——属性在、值也合法,快速检查属性存在与否根本发现不了,因为值是过期的。
两个考试信号值得记住:标签列表里没有任何一个 selected: true,而某个面板可见,说明状态缺失;不是打开标签的那个标签上出现 selected: true,旁边往往还跟着打开标签上的 focused: true,说明状态过期。两种情况都要在切换之后测,而且两个标签都要查,不能只看第一个。
修法:绑定同一个状态
把 aria-selected 绑到驱动激活样式和面板的同一个状态上:激活标签为 true,其余为 false。同一个修法同时修好两个案例。在 React 里,就是让 aria-selected 读 setCase1Active 那套状态;不用框架,就在切换标签的同一处更新属性——不是 CSS 类,也不是写死的 true。
修复后树里的变化:通知面板可见时,案例一中 Account 是 selected: false、通知是 selected: true,通知节点列出 aria-selected: true,Account 节点是 false;案例二中 Account 是 selected: false,通知是 focused: true、selected: true。恰好一个标签被选中,而且就是屏幕上那个。树终于和页面对上了。
VoiceOver 的念法也随之改变。案例二 Account 激活时念“Account,已选中,标签,1/2”。案例一激活标签上的提示是“你当前位于一个已选中的标签上,1/2”。录制里还记录了一次只读状态的念法“已选中”。
需要说明证据边界:修复后激活通知标签的完整 VoiceOver 播报,录制里没有,这里就不引用。修复的证明是上面那棵树——selected: true 恰好落在可见的那个标签上。Account 激活时,未激活的通知标签念的是“通知,标签,2/2。你当前位于一个标签上,2/2。要选择此选项,请按 Control-Option-Space。”
要验证的行为清单:
- 每个标签列表里恰好一个标签是 aria-selected="true",其余都是 "false"
- aria-selected 读的是驱动激活样式和可见面板的同一个状态,没有字面 true 或 false
- 切换标签后再查无障碍树,selected: true 必须跟着可见标签走
- 把 VoiceOver 移到两个标签上分别听,不能只查第一个,也不能只信首次加载时的听感
属性存在不等于正确。标签的 aria-selected 必须跟随真正打开的那个标签。切换标签,查树,别只查耳朵。
热门跟贴