非标准元素需手动实现可访问性与表单集成:必须设 static formAssociated = true、同步 value/checked/disabled、用 role="switch" + aria-checked(字符串值)、禁用时同时设 aria-disabled="true" 和 disabled 属性,toolbar 必须用 div[role="toolbar"] 并满足聚焦与标签要求。

非标准元素(如自定义标签 <x-toggle>、<search-input>)本身不带可访问性,加 role="button" 或 aria-label 不能补全缺失的语义和行为——必须手动桥接 DOM 特性、表单集成与键盘交互,否则屏幕阅读器和键盘用户根本无法感知或操作它。
自定义元素必须满足 formAssociated = true 才能进表单
如果你希望 <x-toggle> 被 form.elements 收录、响应 form.reset()、参与 form.submit() 数据收集,仅靠 Shadow DOM 里放一个 <input type="checkbox"> 不够。漏掉这一步,它在表单中就是个“透明人”:
- 必须在类定义中声明
static formAssociated = true - 构造函数第一行必须调用
super(),否则表单 API 拒绝识别该实例 - 需实现
value、checked、disabled等 getter/setter,并同步到内部input元素 - 浏览器只认这个布尔标志为“是否纳入表单流程”的开关,没有商量余地
data-* 属性不能替代 role / aria-* 的语义职责
data- 属性纯属开发者私有存储,对辅助技术完全不可见。把 data-state="on" 当成可读状态是常见误解:
-
dataset.state只供 JS 读写,不会触发屏幕阅读器播报 - 要让开关被识别为“可切换控件”,必须用
role="switch"+aria-checked,且值要实时同步到属性(不是 dataset) -
aria-checked必须是字符串"true"、"false"或"mixed",不能写布尔值或数字 - 禁用状态必须同时设
aria-disabled="true"和原生disabled属性,二者缺一不可
attributeChangedCallback 中布尔属性值永远是字符串
监听 checked 或 disabled 这类布尔属性时,newValue 是 "true"、null 或空字符串 "",不是 true/false:
立即学习“前端免费学习笔记(深入)”;
- 错误写法:
input.checked = newValue === true→ 永远为false - 正确写法:
input.checked = newValue !== null(对应checked有无属性) - 对
disabled更要小心:disabled=""和disabled="false"都传"",但后者语义上不该禁用 → 应优先依赖属性是否存在,而非值内容 - 建议统一用
this.hasAttribute('disabled')判断,比解析newValue更可靠
toolbar 容器必须用 div[role="toolbar"],不能用自定义标签
<toolbar> 是无效 HTML 标签,浏览器直接忽略其所有 ARIA 行为。工具栏必须用 <div role="toolbar"> 并严格满足条件:
- 必须有
aria-label或aria-labelledby,空字符串或缺失会导致整个容器被跳过 - 子元素只能是可聚焦控件:原生
<button>、<input>、<select>、<textarea> - 禁止用
<a href="#" onclick="..."></a>或<div tabindex="0" @click="..."></div>—— 它们无法被键盘用户自然 Tab 进入,也不触发:focus-visible - 若用自定义按钮(如
<my-button>),它自己必须已实现完整键盘支持(Enter/Space 触发、tabindex="0"、role="button")
最常被忽略的点是:可访问性不是靠堆砌 ARIA 属性实现的,而是靠补全原生能力缺失的部分——焦点管理、键盘响应、表单集成、状态同步,缺一不可。写完自定义元素后,关掉鼠标,全程用 Tab + Enter 测试一遍,比任何代码审查都管用。



















