开发者 Marco Campos 在个人博客里做了一次实验:从零重建一个网页按钮。他没有停在“看起来像按钮”这一步,而是照着 MDNARIA APG 设计模式和 WCAG 2.2,把原生 <button> 元素默认满足的能力拆成 12 项要求,打算逐条手写实现。结果代码从几行 HTML 膨胀成一个完整的 JavaScript 类,而且只写完了一部分。

这不是浏览器厂商发布的新特性,也不是 Web 标准的重大变化,只是一篇工程博客的思想实验。但它戳中一个前端团队经常忽视的问题:自定义组件想追平原生元素,到底要补多少课,这笔账该谁来买单。

12项契约摊开:从角色到表单API,自定义按钮要补什么

Campos 的起点是自定义标签的空白状态。一个新注册的自定义元素,默认行为跟 <span> 没有区别:不可聚焦,没有语义角色,不响应键盘。他把原生 <button> 的默认能力拆成12项,逐项手工补上。

从 span 补到 button:六步累加 1. role="button" —— 辅助技术识别为按钮 2. aria-label —— 补齐可访问名称 3. tabindex="0" —— 进入键盘 Tab 顺序 4. mouse / touch / pointer 事件 —— 点击激活 5. Enter / Space 键盘事件 —— 键盘激活 6. disabled + ElementInternals —— 表单与状态同步

上面六步是主干,完整清单还有细节。12项摊开看更清楚:

#契约项原生 <button>自定义标签起点
1语义角色默认 role 为 button需手动加 role="button"
2可访问名称文本内容自动被读出图标/空文案需要 aria-label
3键盘可聚焦默认进入 Tab 序需要 tabindex="0"
4鼠标点击默认响应需绑定 click 事件
5触摸激活默认响应需绑定 touch 事件
6指针事件默认响应需绑定 pointer 事件
7键盘激活Enter/Space 默认触发需手写 keydown 逻辑
8禁用状态disabled 原生阻断交互要自己实现,还要同步给辅助技术
9表单关联自动关联最近的 form需要 ElementInternals 或手动处理
10提交 name/value原生支持需要额外实现
11表单校验参与原生参与 :invalid 等状态需要额外实现
12新平台API(Popover/Invoker Commands等)浏览器持续跟进需要自己持续追更

前三项解决无障碍问题,对应 WCAG 2.2 的 SC 4.1.2(名称、角色、值)和 SC 2.1.1(键盘可操作)。第9到11项是表单参与,靠的是较新的 ElementInternals API。最后一项——对接 Popover API、Invoker Commands API、Interest Invokers API——不是无障碍规范的要求,是浏览器还在给按钮加新能力,自建实现得自己跟着追。

风险在于,很多团队只做到第1步。加上 role="button" 之后,屏幕阅读器确实会把这个元素读成按钮,但键盘响应、表单提交、disabled 状态不会跟着来。这是12项里最容易被误解的一条:角色变了,契约没变。

原生标签省的不是代码,是维护契约

Campos 实验真正有价值的地方,不是证明写按钮很麻烦,是揭示一个容易被忽视的落差。<button> 默认自带焦点、Enter/Space 激活、表单提交和 disabled 语义,这是浏览器多年演进沉淀下来的跨平台约定。任何自定义标签的默认起点,都更接近一个什么都没有的 <span>

这个差距还在拉大。Campos 提到原生按钮正在对接 Popover API、Invoker Commands API 和 Interest Invokers API 这些新平台能力。自建实现想保持功能对等,就得跟着浏览器的更新节奏持续追,写完一次不算完工。

原文没有说明具体测试过哪些浏览器和读屏器组合,比如 NVDA、VoiceOver、TalkBack——这是想复现验证的读者需要自己补的部分。Campos 也在文章末尾留了没写完的部分,他的代码更像教学示意,不是能直接上线的完整实现。

对前端和选型负责人,分别意味着什么

对前端开发者,这份清单给了一条清楚的默认规则:能用原生元素,就别用自定义标签重写一遍。真正需要自建控件的场景,通常是原生元素覆盖不到的复杂交互,比如自定义下拉列表、富文本工具栏、多选组合控件。如果确实要自建,12项里键盘、表单、禁用状态这几项不能只做视觉还原就上线。

对负责组件库选型和无障碍验收的技术负责人,这份清单更像一份验收单。采购或评审组件库时,应该把键盘操作、屏幕阅读器朗读、表单提交和禁用状态列进测试项,而不是只看视觉还原度。供应商拿不出这几项的测试记录,验收结论就该往后放一放,不要先上线再补测试。

接下来值得盯的变量,是 Popover API 和 Invoker Commands API 这类新特性铺开之后,自定义按钮跟不跟得上迭代节奏。一个团队如果连当前12项都没补齐,新API大概率也会被搁置。