html自定义元素默认无无障碍支持,需手动实现语义、键盘行为和表单集成;role="button"仅声明外观,不提供原生功能;x-toggle必须继承htmlelement、设formassociated=true、桥接真实checkbox;attributechangedcallback中属性值为字符串需正确转换;toolbar容器须用div role="toolbar"并满足聚焦与样式要求。

HTML自定义元素本身不带可访问性,my-button 和 div 在屏幕阅读器眼里完全等价——除非你手动对齐原生语义、补全键盘行为、桥接表单逻辑。
为什么 role="button" 不能替代原生 button
加 role="button" 只是“告诉”辅助技术“这看起来像按钮”,但浏览器不会因此赋予它任何原生能力:
- 空格键/Enter 键无法触发点击(
keydown得自己监听) -
disabled属性不生效,:disabled伪类不会自动匹配 - 焦点管理失控:Tab 进不来、
focus()调用后无视觉反馈、:focus-visible样式不触发 - 表单中完全不可见:
form.elements里查不到,form.reset()不重置它的状态
让 x-toggle 真正支持无障碍的硬性条件
要让自定义开关控件被读屏软件识别为“可切换的控件”,并参与表单流程,必须同时满足以下三点:
- 继承
HTMLElement,且构造函数第一行调用super() - 声明
static formAssociated = true(这是开启表单集成的总开关) - 在 Shadow DOM 内挂载一个真实
input[type="checkbox"],并通过 getter/setter 桥接checked、disabled、value等属性
漏掉任意一项,x-toggle checked 就只是个带样式的 div,不是语义化控件。
attributeChangedCallback 监听布尔属性的坑
比如监听 checked 属性变化时,newValue 实际是字符串 "true" 或 null,不是布尔值:
static get observedAttributes() { return ['checked']; }
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'checked') {
// ❌ 错误写法:newValue === true 永远不成立
// ✅ 正确写法:
const input = this.shadowRoot.querySelector('input');
input.checked = newValue !== null;
}
}
同理,disabled 的判断也得用 newValue !== null,而不是 !!newValue —— 因为 disabled="" 和 disabled="false" 都会传入 "",但后者语义上不该禁用。
工具栏容器必须用 div role="toolbar",不能写 <toolbar></toolbar>
<toolbar></toolbar> 是无效标签,浏览器直接忽略,所有 ARIA 行为失效:
- 必须配
aria-label或aria-labelledby,空字符串或缺失都会被辅助技术跳过 - 子元素只能是可聚焦控件:
button、input、select、textarea;禁止放a或div onclick - 容器需设
tabindex="0"并在首次渲染后调用.focus(),否则键盘用户无法进入 - 必须提供
:focus-visible样式,否则焦点位置不可见
真正难的不是排版,是让每个按钮在 Tab、Shift+Tab、空格、Enter、方向键下表现一致——而这部分没法靠 Shadow DOM 自动完成。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











