shadow dom 不自动处理键盘导航,需组件内部主动实现:确保元素可聚焦且结构正确,监听方向键并管理焦点进出与 slot 动态内容。

Shadow DOM 本身不自动处理键盘导航,所有逻辑必须在组件内部主动实现。外部 Tab 键行为不会穿透 Shadow 边界自动生效,但浏览器默认支持进入 Shadow 内部的可聚焦元素——前提是组件内部结构正确、焦点管理到位。
确保内部元素可被 Tab 访问
键盘导航依赖浏览器原生 Tab 流,而 Shadow DOM 不会阻断它,但需要满足几个前提:
- 内部元素必须是可聚焦的:原生表单控件(
<input>、<button></button>)、带tabindex="0"的容器,或contenteditable="true"元素 - 避免使用
tabindex="-1"(仅用于 JS 聚焦,不参与 Tab 流) - 确保元素未被
display: none、visibility: hidden或disabled状态屏蔽 - Tab 顺序按 HTML 文档流从上到下、从左到右,不是按 JS 插入顺序
手动接管方向键与焦点逻辑
对于选项卡、菜单、轮播等需要 ArrowKeys 控制的组件,不能依赖外部脚本;必须在 shadowRoot 内监听并响应:
- 在
connectedCallback()中为 shadowRoot 或关键容器添加keydown监听器 - 区分
ArrowLeft/ArrowRight(水平切换)、ArrowUp/ArrowDown(垂直切换) - 用
this.shadowRoot.querySelectorAll('[tabindex="0"], button, input')获取可聚焦项列表 - 当前焦点元素可通过
document.activeElement获取,但注意:它返回的是 shadow 内部元素,不是 light DOM 中的宿主 - 调用
element.focus()切换焦点,推荐加{ preventScroll: true }避免意外滚动
处理焦点进出与边界行为
用户 Tab 进入组件时,应有明确的“首入焦点”;离开时,也需决定是否将焦点交还给外部或保持在组件内:
- 首次进入时,可默认聚焦第一个可聚焦子元素(如首个 tab 标题或输入框)
- Tab 到最后一个元素后继续按 Tab,应聚焦第一个;Shift+Tab 到第一个后继续,应聚焦最后一个(环形导航)
- 若组件是模态框类结构,需配合 focus trap(如监听
focusin事件拦截外部焦点) - 不要依赖
delegatesFocus实现键盘导航——它只响应点击,对 Tab 完全无效
兼容 slot 内容的动态焦点管理
当组件通过 <slot></slot> 接收用户传入的内容时,这些内容可能含可聚焦元素,但它们的 Tab 顺序由 light DOM 决定,且 slot 不自动更新节点引用:
- 监听
slotchange事件,在回调中重新收集所有slot.assignedNodes({ flatten: true })中的可聚焦节点 - 避免直接用
querySelectorAll查 slot 元素——它查的是 shadow 内部结构,而非投射后的真实 DOM - 若用户插入新按钮或链接,需在
slotchange后重置焦点索引缓存,否则方向键可能跳过新增项 - 对 slot 投射内容做最小干预:不强制改 class 或 tabindex,只读取并纳入导航链











