无障碍的智能体界面

8 分钟读完

H19
实战手册 · 智能体体验与人机交互

无障碍的智能体界面。

一个智能体界面可以通过每一项自动化无障碍检查,却依然没法用读屏软件操作,因为这里坏掉的东西没有一样是标记——都是时序。辅助技术假定页面会安定下来;而智能体产出的页面从不安定,然后又把唯一真正要紧的那个控件——审批——放在一个倒计时后面。把时序问题修好,清单上的问题会自己解决;只修清单,你交付的就是一份绿色审计报告,盖在一个没人能驾驶的界面上。

STEP 1

这些失败是时序性的,所以你的审计漏掉了它们。

自动化工具检查的是一张快照:对比度、标签关联、alt 文本、标题层级、焦点轮廓。这里头每一项都值得通过,而它们没有一项描述了智能体界面里出错的东西。

  • 一股 token 流每秒往 DOM 上追加几十次。如果那块区域是活动区域(live region),读屏软件要么被淹没——在用户自己的导航之上念出零碎片段——要么以不可预测的方式合并,把答案的中段丢掉。两种都没法用;而只有一种能在 demo 里被看出来。
  • 页面没有一个辅助技术可以观察到的"完成"事件。视力正常的用户看见光标停了。用读屏软件的用户则没有等价的信号,除非你造一个出来。
  • 控件在运行途中出现。焦点在某处;一个新的"批准"按钮在别处冒出来;没有任何东西告诉用户它存在,而等他们找到时,底下的状态已经变了。
  • 输出的形状每次运行都不一样,于是这个界面没有任何东西可供习得。这正是生成式界面最少被讨论的那笔代价。

最常见的那个缺陷:把流式答案本身接到 aria-live 上。这是最符合直觉的动作——有新内容,就播报它——而它造出的界面会连着四十秒盖过自己的用户说话。永远不要把 token 流做成活动区域。

STEP 2

在语义边界上播报,而不是在 token 边界上。

把界面劈成两块职责不同的区域,因为它们的时序行为不同。

  • 状态区——aria-live="polite",每个有意义的事件更新一次,且大致不超过每几秒一次。"正在检索代码库。""找到 12 个文件。""等待你的批准。"这是旁白,它应该简练到一个人能一边做别的事一边跟得住。
  • 答案区——完全不是活动区域。它是一份文档。给它一个角色和一个标签,在它完成时播报一次并说明它有多长,然后让用户用自己的工具、按自己的节奏、以自己的阅读顺序去浏览它。

完成信号是最常被跳过、代价也最大的那一块。没有它,用户分不清"还在想""已经完成"和"已经崩了",于是他们只能轮询——方向键进入区域、读、退出来、等、再来一遍。请在状态区里显式播报完成,并把 token 数或行数放进那句播报里,好让他们决定现在要不要读。

aria-live="assertive" 只留给恰好一类事件:需要用户立刻行动的,或者报告一次他们必须知道的失败。如果什么都是 assertive,这个界面就是在嚷嚷,而用户会把它关掉。

STEP 3

审批弹窗是一项会变成障碍的安全功能。

这里是利益真正冲突的地方。审批与确认体验要的是摩擦、紧迫感和一份清楚的 diff;无障碍要的是时间、稳定的焦点,以及被线性朗读后仍然成立的内容。在这个冲突上朝着那个显而易见的方向做错,结果就是:最需要复核这个动作的人,恰恰是那个没法复核的人。

  • 把焦点移进弹窗,并把它困在里面。一个出现时不夺取焦点的审批,对键盘用户而言等于不存在。关闭时把焦点还给唤起它的那个控件。
  • 永远不要给不可逆的动作装计时器。"30 秒后自动批准"是一块秒表做出的决定,而它精确地歧视了读得最慢的那批用户。如果无人值守的运行确实需要一个默认值,就把默认设成拒绝,并在弹窗里写明。
  • 让 diff 可读,而不只是可见。只靠颜色的 diff 对读屏软件什么也没传达,对色觉障碍用户也传达不了多少。给每行加一个文字前缀、给一个计数("3 处新增,1 处删除"),并在 diff 上方用平实的语言写一句后果摘要——这对所有人都更好,而且本来就是视力正常的用户实际会读的那一句。
  • 说清楚"什么都不做会怎样"。"在你批准之前,这不会执行"是界面欠用户的一句话,也正是这句话让"花时间读一读"变得安全。

自动批准计时器是最容易被当成功能来辩护的无障碍缺陷。如果理由是"不然运行会卡住",答案是异步交接——参见异步智能体体验——而不是一个悄悄把人类决定转换成超时的倒计时。

STEP 4

凡是里面装了钟的,都要留一个逃生口。

智能体界面里塞满了没人当成需求写下来的隐含时序假设。

  • 中断窗口。如果"停止"只在转圈图标可见时有效,那些需要更长时间才能定位到控件的用户就停不下这个智能体。让中止在整段运行期间、以及结束后的一段宽限期内始终可用。
  • 语音插话打断。一个被调成"第一个音节就打断"的语音智能体,会把有言语障碍或语速较慢的用户拦在句子中间。断句阈值是一项无障碍设置;把它暴露出来。
  • 会话与空闲超时。一个长时间运行的智能体任务加上一个空闲超时,等于任何离开一下或做事较慢的人丢掉工作成果。到期前先警告,并提供延长。
  • 自动滚动。跟着流走的滚动,会跟一个已经往上翻去阅读的用户打架。用户一动就停止自动滚动,并提供一个显式的"跳到最新"。
  • 动效。在打字机效果、微光占位符和进度动画上遵守 prefers-reduced-motion。一个闪四十秒的光标效果是一个前庭问题,不是一份惊喜。
STEP 5

非确定性是一个认知无障碍问题。

任何常规界面都可以被习得。控件明天还在同一个位置,流程还是同样的步数,熟练度会积累。而一个智能体界面可以在连续两次运行里把这三条全部违反,其代价最重地落在有认知障碍的用户、有记忆障碍的用户身上——以及悄悄地,落在每一个赶时间的人身上。

缓解之道是:即使内容在动,也要把框架按住不动。

  • 固定的地标。状态、输出、动作与历史每次运行都住在同样的区域里、带同样的标签,哪怕某个区域是空的。一个有标签的空区域,比一个有时干脆不存在的区域好用。
  • 稳定的动作词汇。同一个操作每次都用同一个动词。不要让模型去给按钮命名——那是生成式界面递过来的捷径,而它会毁掉可习得性。
  • 可预测的排序。动作按固定顺序排列,且那个破坏性的动作绝不出现在上一次运行里安全动作所在的位置。
  • 一个平实语言模式。一个开关,去掉推理过程,用短句显示智能体做了什么、想要什么。它的用处远远超出它本来的目标人群——而这通常正是检验一项无障碍功能是被设计出来的、还是被硬拧上去的那把尺子。
STEP 6

按它失败的方式去测:在时间里测,用真正的工具测。

静态分析抓不到上面任何一条,所以一份全绿的 axe 报告在这里几乎什么也没告诉你。能找出真缺陷的测试是行为性的,而且一旦你下决心去做,它们都很便宜:

  • 用读屏软件把一个真实任务从头跑到尾——NVDA 或 VoiceOver,把显示器关掉。不是一个静态页面:是一次完整的运行,带流式输出、一次工具调用、一次审批和一次撤销。多数团队在头十五分钟里就能找到三个阻塞级缺陷,而第一个通常就是活动区域。
  • 只用键盘完成一次"批准并撤销"的完整循环。如果你够不到审批、读不了 diff、拒绝不了它、也恢复不过来,那么无论审计怎么说,这条流程都是坏的。
  • 把播报序列快照成一份固件。录下一次脚本化运行期间被播报的内容,并在 CI 里对它做断言。这是唯一能在半年后有人又把答案区改回活动区域时抓住他的回归测试。
  • 用一次慢运行和一次失败运行来测。九十秒的延迟和一次运行中途的工具报错,正是状态区要么挣得它的位置、要么暴露出它从来只旁白了顺利路径的地方。

如果只做一件事:把流式答案从 aria-live 里拿出来,加一块在语义边界上播报的简练状态区,并显式播报完成。仅这一处改动,就能把最常见的那种智能体界面从"读屏软件下无法使用"变成"可以浏览",而它只需要一个下午。然后去把你的自动批准计时器删掉——那是另一个清单永远不会标出、而真实用户第一天就会撞上的缺陷。