打开网易新闻 查看精彩图片

2023年WebAIM调研显示,97.4%的网站存在无障碍(Accessibility)缺陷,而弹窗通知(Toast Notification)是重灾区中的重灾区。你可能没意识到,那个右上角一闪而过的"操作成功",对屏幕阅读器用户来说,要么完全听不到,要么粗暴打断当前操作——这种体验相当于明眼人打字时突然被蒙住眼睛。

屏幕阅读器用户的"幽灵弹窗"困境

React生态里,Toast组件的实现方案少说有几千个。但大部分开发者只关心两件事:能不能自动消失,样式好不好看。视障用户用什么体验?不在考虑范围内。

问题出在ARIA(Accessible Rich Internet Applications,无障碍富互联网应用规范)的"实时区域"(Live Region)机制。简单说,这是浏览器告诉屏幕阅读器"这里有新内容"的管道。但管道怎么用,直接决定用户是收到贴心提醒,还是被信息轰炸。

错误示范:把Toast容器设为aria-live="assertive"

这个属性会强制打断用户当前听到的所有内容。想象一下,你正在填写一份长表单,屏幕阅读器逐字朗读每个字段。突然一个"点赞成功"的Toast冲进来,把你正在听的地址信息切成两半。你得重新把焦点移回表单,从头听起。

更隐蔽的坑是动态插入。很多开发者习惯把Toast直接append到body末尾,而不是预先放置Live Region容器。结果是:屏幕阅读器根本不知道有新内容出现,弹窗成了"幽灵"——明眼人看得见,视障用户完全感知不到。

正确的做法是在组件挂载时就创建Live Region,设置aria-live="polite"。这个值让通知排队等待,当前任务结束后再播报。对键盘用户来说,这相当于把"插话"改成"等你说完再递纸条"。

3秒消失的陷阱:为什么自动关闭是 accessibility 噩梦

3秒消失的陷阱:为什么自动关闭是 accessibility 噩梦

业内默认的Toast生命周期通常是3-5秒自动消失。这个数字对明眼人刚好扫完一行字,但对依赖屏幕阅读器的用户,可能刚听到"系统提示"四个字,内容就没了。

WCAG(Web Content Accessibility Guidelines,网页内容无障碍指南)2.1有一条硬性规定:自动更新的内容必须能被用户暂停、停止或隐藏。不是建议,是A级合规要求。

打开网易新闻 查看精彩图片

实现方案需要同时监听两组事件:

鼠标悬停(onMouseEnter/onMouseLeave)和键盘焦点(onFocus/onBlur)。只做一个等于把轮椅坡道修了一半——鼠标用户能暂停,纯键盘用户照样被计时器追着跑。

暂停逻辑要绑定在Toast容器本身,而不是关闭按钮

有些实现把暂停触发器放在那个"X"按钮上,意味着用户必须先把焦点移到关闭按钮才能暂停。这设计相当于火警警报响了,但暂停开关在房间另一头的灭火器旁边。

更好的做法是:整个Toast卡片都是可交互区域,焦点进入即暂停,离开才恢复倒计时。同时保留手动关闭的明确入口,让用户拥有双重控制权。

焦点管理:Toast最危险的"隐形盗窃"

焦点管理:Toast最危险的"隐形盗窃"

2019年一个著名案例:某电商大促期间,视障用户下单时被连续弹出的优惠券Toast打断,焦点被强行拉到通知区域,导致支付密码输入到错误的表单字段。结果?订单取消,用户投诉,平台道歉。

问题的根源是开发者调用了element.focus()把键盘焦点主动移到Toast上。这个操作对明眼人无感知,但对屏幕阅读器用户,相当于有人突然抢走你手里的笔,塞给你一张传单。

正确的行为是:Toast出现时不移动焦点,用户当前操作完全不受影响。如果用户想处理通知,自行导航过去;如果不想理,继续打字就行。这种"被动存在感"才是无障碍设计的核心——提供服务,但不强迫注意。

技术实现上需要检查document.activeElement,确保Toast渲染前后焦点位置不变。同时给Toast容器设置tabIndex="-1",让它可以被脚本聚焦(方便用户主动跳转),但不会进入默认Tab顺序。

关闭按钮的"X"之谜:图标≠可访问性

关闭按钮的"X"之谜:图标≠可访问性

打开网易新闻 查看精彩图片

几乎每个Toast右上角都有个关闭图标,通常是SVG画的"X"或者字体图标。但代码层面,这个按钮可能只是一段没有文本内容的。屏幕阅读器遇到这种元素,会沉默跳过,或者读出"按钮,未标记"——用户知道有个按钮,但完全不知道按下去会发生什么。

解决方案是aria-label="关闭通知",或者视觉上隐藏但屏幕阅读器可见的文本关闭。不要依赖title属性,它在触摸设备和很多屏幕阅读器场景下不可见。

另一个细节:关闭按钮必须在Toast获得焦点时可用。有些设计把按钮默认隐藏,hover才显示。对键盘用户来说,这意味着他们永远找不到关闭入口——因为键盘触发不了hover状态。

堆叠通知的"信息洪水"防御

堆叠通知的"信息洪水"防御

现代UI允许同时存在多个Toast,像叠罗汉一样从角落冒出来。这个设计对明眼人是信息密度提升,对屏幕阅读器用户可能是灾难——如果10条通知同时涌入Live Region,阅读器会按顺序全部朗读,形成长达几分钟的"音频垃圾"。

需要设置硬性上限,比如最多3条可见,旧通知自动进入历史区域或批量合并。同时给容器设置aria-atomic="false",让阅读器只播报变化的部分,而不是把整个通知列表重读一遍。

更精细的做法是优先级分级:错误类Toast用assertive打断当前任务,成功类用polite排队等待,信息类直接静默写入历史。这套规则需要产品经理和开发者共同制定,不是纯技术决策。

React社区有个值得参考的实现模式:用自定义Hook管理Toast队列,把无障碍逻辑(Live Region、焦点管理、暂停计时)和UI渲染彻底分离。这样即使设计师把Toast改成从底部滑入,底层 accessibility 机制不需要重写。

这套方案的核心代码量其实不多——一个useEffect监听焦点,一个setInterval封装暂停逻辑,一个固定的Live Region容器。但难的是意识:开发阶段就要把屏幕阅读器、键盘-only用户、认知障碍人群纳入测试流程,而不是上线后补票。

Chrome DevTools现在内置了Lighthouse无障碍审计,但自动化工具只能 catching 30%左右的问题。真正的检验是关掉显示器,只用Tab键和屏幕阅读器完成核心任务——如果这时候Toast不会打断你、不会消失太快、关闭按钮找得到,才算及格。

最后留一个开放问题:你上次写Toast组件时,有没有测试过连续按Tab键20次后的焦点路径?还是直接假设"用户用鼠标点一下关闭就行了"?