自定义元素无默认语义,需显式设置role、aria-label等属性;attributeChangedCallback不触发初始值,应结合connectedCallback双重渲染;Shadow DOM隔离语义,标题需通过slot传入或手动设aria-level;扩展原生元素时须保留可访问名称和原生交互行为。

自定义元素本身不自动增强语义——它只是容器,语义是否成立,取决于你往里塞什么、怎么暴露给辅助技术。
customElements.define() 注册后,元素默认是 role="generic"
浏览器不会因为你写了 <date-picker></date-picker> 就自动把它识别为“日期选择器”。DOM 中它只是一个无语义的原子节点,屏幕阅读器读作“group”或直接跳过。要让它具备语义,必须显式声明:
- 用
role属性补足缺失的语义角色,比如<date-picker role="application">(适用于复杂交互控件) - 通过
aria-label或aria-labelledby提供可访问名称,否则<status-badge>这类无文本内容的元素会被读作“blank” - 若封装的是表单控件(如
<my-input>),必须透传name、required等原生属性,并在内部<input>上同步设置,否则表单提交和无障碍 API 均失效
constructor 里不能读 getAttribute(),但 connectedCallback 里也未必可靠
常见错误是:在 connectedCallback 中直接 this.getAttribute('label') 渲染,结果首次加载时值为空。原因有二:
- HTML 解析与 JS 执行存在微小时序差,属性可能尚未被解析完成(尤其在内联 HTML 中)
-
attributeChangedCallback不触发初始值,只响应后续变更
稳妥做法是:在 connectedCallback 中先尝试读取,再立即调用一次 this._render();同时在 attributeChangedCallback 中再次调用 _render(),形成双重保障。
立即学习“前端免费学习笔记(深入)”;
Shadow DOM 隔离样式,也隔离了语义继承链
把 <h2> 放进 Shadow DOM 后,它不再参与外部文档的标题层级(document.querySelector('h2') 查不到),也不会被屏幕阅读器按文档结构朗读。解决路径很明确:
- 若组件内需标题语义(如
<faq-item>的问题标题),必须用<slot>让用户传入真实<h3>,而非在 shadow 内部硬写<h3> - 若必须封装标题逻辑(如自动生成编号),需手动设置
aria-level="3"并确保父级容器有正确role(如role="region"+aria-labelledby) - 避免在 shadow 内使用
<main>、<header>等具有全局语义的标签——它们在 shadow 中无效,且会干扰外部语义流
用 is="xxx" 扩展原生元素时,语义继承但行为不自动继承
写 <button is="my-button"> 确实能继承 button 的所有语义(role="button"、键盘支持、表单关联),但有个关键陷阱:
- 你重写的
connectedCallback里如果调用了this.innerHTML = '...',会清空原生按钮的可访问名称(textContent被覆盖),导致屏幕阅读器读不出按钮文字 - 若添加了自定义事件监听(如
click),必须保留原生行为:不能event.preventDefault()掉所有,否则键盘用户按 Space/Enter 无法触发 -
formAssociated: true是个高级选项,启用后需手动实现formAssociatedCallback和validate(),否则checkValidity()始终返回true
真正难的不是让标签看起来像语义化,而是让它的行为、状态、焦点管理、键盘导航、错误反馈全部对齐标准控件——这些细节一旦漏掉,语义就只剩一层皮。



















