跳到主要内容

无障碍树是你最新的公共 API

· 阅读需 11 分钟
Tian Pan
Software Engineer

十年来,无障碍(Accessibility)一直是那种被忽视的工作。它躺在待办事项(backlog)的最底层,只有在合规性审查期间才会浮出水面,然后被修补上刚好能让 linter 闭嘴的 ARIA 标签。经济层面的论据从未奏效,因为受影响的用户是少数群体,他们的流失从未出现在任何会让工程师收到告警(paged)的仪表盘上。

然后,浏览器代理(browser agents)出现了,经济逻辑在一夜之间发生了反转。像 ChatGPT 的浏览模式、Claude 的 computer use 以及基于 Playwright-MCP 的自动化浪潮,它们并不看你的像素。它们读取的是浏览器的“无障碍树”(accessibility tree)——这正是屏幕阅读器(screen readers)二十年来一直在消费的语义结构。每一个未标记的按钮、每一个伪装成链接的 div、每一个没有暴露状态的自定义下拉菜单,现在不仅对盲人用户不可见,对你的业务合作伙伴正在集成的代理也是不可见的。十年积压的无障碍债务变成了集成债务(integration debt),而集成债务是挂钩付费客户的。

令人不安的并非技术本身,而是激励机制的转变:那些多年来忽视屏幕阅读器用户的团队,将在代理合作伙伴关系破裂的那一周,在一个迭代周期(sprint)内修复这些完全相同的 bug。无论这让你感到愤世嫉俗还是充满希望,它都改变了你思考标记语言(markup)的方式。你的无障碍树不再仅仅是一个辅助技术细节。它是一个公共 API 表面——未经版本化、没有文档,且已经在生产环境中运行。

代理读取的是树,而非屏幕

当浏览器渲染页面时,它会构建两个并行结构。渲染树(render tree)负责像素。无障碍树(accessibility tree)负责语义:这是一个经过剪裁的层级结构,其中每个节点都携带一个角色(按钮、链接、文本框)、一个可访问名称、一个状态(已选中、已禁用、已展开)以及一个描述。屏幕阅读器通过平台 API 消费它。浏览器代理则通过 Chrome DevTools Protocol 或框架快照来消费。

代理之所以在无障碍树上达成标准化,是因为 Token 经济学,而非理想主义。一个典型页面的原始 DOM 导出(dump)通常超过 15,000 个 Token。通过视觉模型(vision model)推送的截图会消耗数千个 Token,并且容易误读密集的布局。而同一页面的无障碍树快照通常只需 200–400 个 Token。Microsoft 的 Playwright MCP 服务器会向模型发送一个 YAML 格式的 tree,其中每个交互元素都有一个编号 ID;代理通过说“点击 ID 12”来采取行动,而不是去寻找 CSS 选择器。browser-use、Claude 的 read-page 动作以及过去两年中发布的大多数 MCP 浏览器服务器的工作方式都大同小异。Google 的 Project Mariner 采用视觉加树的混合方案,即便如此,树依然承担了语义负载。

这带来了一个直接的后果:如果一个元素没有在无障碍树中正确呈现,代理就无法可靠地找到它。有些工具甚至更加严格——Vercel 的代理浏览器通过遍历树并匹配角色和名称来解析引用,因此没有正确角色的元素根本无法获得引用。它不是难以点击,而是根本不存在。

你的 Div 汤(Div Soup)现在返回的是 404

将经典的无障碍缺陷映射到代理行为上,每一个都会变成熟悉的 API 故障:

  • 没有角色的可点击 div 是一个未记录的端点。一个样式像按钮的 <div onClick={...}> 渲染正常,对鼠标用户也有效。但在树中,它是一个既没有角色也没有名称的通用容器。当代理被要求“加入购物车”时,它会扫描树以寻找类似按钮的东西,结果一无所获,要么任务失败,要么点错了别的东西。
  • 未标记的表单字段 是一个没有名称的参数。一个仅通过视觉接近与其标签关联的输入框,在树中显示为 textbox: ""。正在填写结账表单的代理必须猜测哪个空白框是邮政编码。有时它会猜错,而且与人类不同,它会自信地提交。
  • 空图标按钮 是一个名称被剥离的方法。一个内容是 SVG 且没有可访问名称的按钮会显示为 button: ""。那是关闭、提交还是删除?代理通过结果来发现真相,就像屏幕阅读器用户一样。
  • 仅存在于 CSS 中的状态 是一个从未到达的响应体。一个通过类名标记开启/关闭、却没带 aria-expanded 的自定义下拉菜单,让代理无法得知点击是否生效。它会再次点击,结果菜单又关闭了。

这种情况的规模并非假设。WebAIM 对全球前 100 万个主页的年度爬取显示,约 95% 的页面存在可检测到的 WCAG 故障——在 2025 年的运行中,每页平均约有 51 个错误,其中一半的页面缺失表单标签,近三分之一的页面存在空按钮。过去,这些数字中的每一个都代表着少数用户体验的下降。而现在,在某些网络中机器人驱动的请求已超过 HTML 流量 50% 的当下,每一个数字都是任何触达你网站的自动化工作流的故障模式。

增加 ARIA 通常是错误的修复方案

反射性的反应——随意堆砌 ARIA 属性直到通过审计——往往会让情况变得更糟,数据清楚地证明了这一点。WebAIM 发现 ARIA 的使用量每年增长约 18%,达到每页约 106 个属性,而使用 ARIA 的页面平均可检测到的错误是未使用页面的两倍(57 个对 27 个)。在大约三分之一使用 ARIA 构建的菜单中引入了新的障碍:没有必要键盘交互的角色、永远不会更新的状态,以及指向不存在 ID 的标签。

这些数字背后的模式是“补丁式修复”。ARIA 被当作油漆粉刷在非语义化的标记上,而这样做的人通常并不会根据它生成的树进行测试。对于智能体(Agent)来说,错误的角色比没有角色更糟糕,原因与错误的 API 文档比没有文档更糟糕一样:智能体信任它。在一个仅响应鼠标事件的 div 上声明 role="button",智能体的合成交互就会失败,这种失败比缺失元素要难诊断得多。

真正有效的修复方案其实很枯燥:语义化 HTML 优先。原生的 <button> 免费为你提供了角色、名称计算、焦点行为和键盘激活——并保证在不同浏览器中保持一致。<nav><main><label for> 和标题层级承载了页面的大部分结构,而无需单个 ARIA 属性。请将 ARIA 留给真正的自定义组件,当你使用它时,请履行完整的契约——包括角色、状态和键盘行为——而不只是满足扫描器要求的那个属性。这是无障碍从业者十五年来一直坚持的建议。不同之处在于,现在你语义信息的消费者包括了那些会提交错误报告并放弃购买流程的软件。

像审计 API 合约一样审计树

如果树是一个 API,就请像测试 API 一样测试它。工具已经存在;它是为无障碍工程师构建的,且无需更改即可用于智能体就绪度测试。

打开 Chrome 开发者工具,开启完整的无障碍树视图,像智能体一样审视你的关键流程。更好的做法是:在你的结账、注册和搜索页面上运行 Playwright 的 ARIA 快照,并阅读它输出的 YAML。那个 YAML 几乎逐字节地构成了你的页面在某人智能体中所占用的上下文窗口。在流程的每一步中,有三个问题至关重要:

  1. 每个必需的操作都能被命名吗? 如果树显示 button: "",或者在主要的行动召唤按钮(CTA)处显示一个空白的通用节点,智能体就无法被指示去点击它,除非通过脆弱的位置推测。
  2. 每个状态转换在树中都是可观察的吗? 加载状态、验证错误、展开的菜单、添加到购物车的物品——如果唯一的证据是视觉上的,智能体在操作之间就像是在盲飞。活跃区域(Live regions)和 ARIA 状态属性是树“返回响应”的方式。
  3. 树说的是实话吗? 陈旧的 aria-expanded 值、包裹了实际可交互内容的 aria-hidden、与可见文本不再匹配的标签——每一个都是你的 API 对其消费者撒的谎,而智能体是很容易受骗的消费者。

然后将快照作为回归测试产物。团队已经在 CI 中对比 ARIA 快照以捕捉视觉重构导致的回归;同样的对比也能捕捉到那些默默将“下订单”重命名为无标签图标的重新设计。针对 Web 智能体的研究证实了你的预期:在语义清晰的页面上,任务成功率远高于不可访问的页面——一项研究测量出在完全可访问的页面上成功率为 78%,而随着语义质量的下降,成功率急剧下降。你对智能体中介流量的转化率,从初步估算来看,就是你树质量的一个函数。

在树本身之外,还有一个战略层。Cloudflare 现在根据智能体就绪度为网站评分;像“为智能体构建 Web”之类的研究提案主张在人类 UI 之外提供显式的机器可读操作规范。这些标准需要数年才能定型。而无障碍树是现今存在于每个浏览器中、拥有二十年工具支持的接口。

那些一直以来都是正确的用户

这里有一个值得深思的苦涩讽刺。盲人用户多年来一直在报告那个无标签的按钮,却被降低了优先级。而当一个智能体集成在同一个按钮上失败时,它在周四前就被修复了。这种激励机制的反转是真实的,而且并不怎么光彩。

但请接受这种交易。每一个让你的网站对智能体易读的修复,都会让它在屏幕阅读器中表现得更好,因为它们本质上是同一棵树。智能体需要的键盘可操作性,正是运动障碍用户所需要的键盘可操作性。智能体就绪度是第一个直接挂钩收入的无障碍业务案例,而受益者包括了旧业务案例未能覆盖的所有人。

实际的建议是:从现在开始,将你的无障碍树视为一个版本化的公共接口。在 CI 中对其进行快照。在涉及 UI 的 PR 中审查树的差异。将“在无障碍树中正确渲染”放入你的完成定义(DoD)中,紧挨着“在移动端正确渲染”。智能体已经在阅读了。唯一的问题是,你的网站是否说了一些它们能够解析的内容。

References:Let's stay in touch and Follow me for more thoughts and updates