自定义标签在chrome/firefox/safari中被当作htmlelement处理,不报错但无默认语义:node.role为空、getcomputedstyle(el).display为inline、不参与sectioning、无障碍树中无name/role,屏幕阅读器通常跳过或仅读作“group”。

自定义标签在Chrome/Firefox/Safari里实际被当什么处理
浏览器不会报错,但会把它当HTMLElement,且node.role为空、getComputedStyle(el).display默认是inline。它不参与语义结构(比如sectioning)、不触发landmark角色、无障碍树里没有name或role,屏幕阅读器通常跳过或只读作“group”。
常见错误现象:写了<my-header></my-header>,CSS设了width: 100%却没效果——因为默认display: inline,块级行为必须显式声明;JS里document.querySelector('my-header')能取到,但el.tagName是"MY-HEADER"(全大写),el.constructor.name仍是HTMLElement。
- DOM节点类型始终是
HTMLElement,不是HTMLUnknownElement(那是IE8及以下才用的) - 所有自定义标签都走“void element fallback”路径:不报错、不警告、不注入任何默认语义
- 想让它撑满父容器?必须加
display: block或display: flex
为什么<custom-section></custom-section>不能替代<section></section>做语义划分
根本原因在于浏览器的sectioning逻辑只响应白名单标签。哪怕你给<custom-section></custom-section>加上role="region"和aria-labelledby,它也不会成为sectioning root。
典型影响有两处:<h1></h1>在<custom-section></custom-section>内部仍算整个文档的最高级标题(不会重置层级计数);document.querySelectorAll(':scope > h2')这种节内选择器也完全失效——因为:scope不认自定义标签为作用域根。
-
<section></section>会自动创建新的outline上下文,影响标题隐式编号和大纲生成 -
<custom-section role="region"></custom-section>只是告诉辅助技术“这是个区域”,但不改变 heading 流程 - 搜索引擎只基于标准标签判断内容结构权重,自定义标签的内容会被抓取,但无结构信号
坚持用自定义标签时必须手动补全的ARIA要素
光写role远远不够。例如<my-header role="banner"></my-header>,若没配套aria-label或aria-labelledby,屏幕阅读器仍可能读作“banner”,无法说明这是“网站头部”还是“文章头部”。
真正容易被忽略的是命名链断裂:如果用了aria-labelledby="header-title",但id="header-title"的元素不存在或不可访问(比如display: none),整个role就形同虚设。
- 基础语义三件套:
role+aria-label(或aria-labelledby) + 确保关联文本可读(非aria-hidden="true") - 导航区域必须含至少一个可聚焦链接,否则
role="navigation"会被辅助技术忽略 - 主内容区只能有一个
role="main",多写会导致WCAG失败 -
role="article"有严格要求:内容需独立可分发、有明确作者/发布时间,不是所有卡片都适用
Web Components场景下如何避免语义丢失
自定义元素(如class MyHeader extends HTMLElement)注册后,DOM中仍是HTMLElement实例,语义不会自动附着。降级风险集中在两点:一是Shadow DOM隔离导致外部ARIA属性无法穿透;二是slot内容若未显式绑定aria-labelledby,父组件的语义信息就断了。
实操建议优先用attachShadow({mode: 'closed'})配合delegatesFocus: true,并在connectedCallback里动态补全关键属性:
connectedCallback() {
if (!this.hasAttribute('role')) this.setAttribute('role', 'banner');
if (!this.hasAttribute('aria-label')) {
const title = this.querySelector('[slot="title"]')?.textContent || 'Header';
this.setAttribute('aria-label', title);
}
}
- 别依赖
slot内容自带语义——它在Light DOM里,而Shadow DOM是独立边界 - 避免在
shadowRoot里直接写<h1></h1>,除非你同步控制其与外部大纲的层级关系 - 用
ElementInternals(Chrome 95+)可更可靠地暴露可访问性状态,但IE/旧Safari不支持,得降级兜底
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











