自定义元素默认不可访问,因浏览器不自动赋予role或语义;必须手动添加role、aria-*属性、tabindex及键盘事件处理,并确保shadow dom内id引用有效。

自定义元素(customElement)本身不带任何无障碍语义,必须手动补全角色、状态、名称和键盘行为——否则对屏幕阅读器用户来说,它就是个“看不见也点不了”的空盒子。
为什么 defineElement 创建的组件默认不可访问?
浏览器不会给 customElement 自动分配 role 或隐式语义。哪怕你叫它 <my-button></my-button>,它在辅助技术眼里仍是 <my-button></my-button> —— 一个未定义角色的未知标签,读屏器只会说“自定义元素”,不朗读内容,不响应 Tab,也不支持 Enter/Space 激活。
常见错误现象:
- 用
my-toggle替代<input type="checkbox">,但没设role="switch"和aria-checked - 自定义
my-tab-group没加role="tablist",Tab 键无法进入,VoiceOver 直接跳过整块区域 - 组件内文本可读,但焦点无法到达——忘了加
tabindex="0"或没处理键盘事件
role、aria- 属性和原生语义怎么配?
不能只写 role="button" 就完事。role 只是告诉读屏器“这是什么”,但用户还得能操作它、感知它的状态、理解它的用途。
必须同步满足这四点:
- 用
role明确类型(如role="combobox"、role="slider") - 用
aria-labelledby或aria-label提供可访问名称(优先选aria-labelledby,避免硬编码) - 用
aria-*状态属性反映实时变化(如aria-expanded="true"、aria-checked="false") - 用
tabindex="0"+ 键盘事件(keydown监听Enter/Space/ArrowDown等)实现可聚焦与可操作
反例:<my-slider role="slider"></my-slider> 缺少 aria-valuenow、aria-valuemin、aria-valuemax,读屏器只知“这是滑块”,不知当前值、范围、是否可调。
如何让键盘导航真正可用?
自定义组件的键盘逻辑不是“加个 tabindex”就能跑通的。它得符合 WAI-ARIA Authoring Practices 的交互约定。
典型场景要求:
-
my-tab-group:Tab 进入后,用ArrowLeft/ArrowRight切换 tab,Home/End跳首尾,Enter或Space激活当前 tab 面板 -
my-dropdown:Tab 进入触发按钮后,ArrowDown展开并聚焦第一个选项,Escape关闭并返回触发器 -
my-dialog:打开时用inert或aria-hidden="true"隐藏背景(注意:别直接设在上),同时把焦点强制移到对话框内首个可聚焦元素
容易被忽略的一点:所有键盘事件必须 preventDefault(),否则可能触发浏览器默认行为(比如 Space 滚动页面)。
测试环节最容易漏掉什么?
DevTools 的 Accessibility 面板只检查属性是否存在,不验证逻辑是否生效。真实障碍往往出在“状态不同步”或“焦点丢失”上。
务必实测以下三类场景:
- 用 NVDA 或 VoiceOver 打开/关闭组件,确认朗读内容与视觉一致(比如展开后是否播报“已展开”,折叠后是否说“已折叠”)
- 纯键盘操作全流程:Tab 进入 → 方向键切换 → Enter 激活 → Escape 退出 → Tab 继续到下一个控件
- 动态更新后是否触发
aria-live:比如搜索建议列表插入新项,是否被读出;表单校验失败后,错误信息是否通过aria-describedby关联到对应输入框
最常被绕过的复杂点:自定义元素内部若含 Shadow DOM,aria-labelledby 指向的 ID 必须在同一个 Shadow Root 内,跨边界引用会失效——这点连很多 Lighthouse 检查都覆盖不到。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











