根本原因是浏览器只做单层焦点委托:父组件 delegatesfocus=true 时,点击仅聚焦子组件宿主,不会自动进入其 shadow 内部;需手动补链——子宿主设 tabindex、内部有可聚焦元素,并在 focusin 中调用 shadowroot.queryselector().focus()。

嵌套 Shadow DOM 时,子组件无法响应点击或聚焦?
根本原因不是“不支持嵌套”,而是浏览器只做单层委托:父组件设 delegatesFocus: true,点击后焦点只会落到子组件宿主元素上,不会继续钻进子组件的 Shadow 内部。比如 <my-card><my-input></my-input></my-card>,点 <my-card></my-card> 只会让 <my-input></my-input> 获得焦点,但 <my-input></my-input> 自身 Shadow 里的 <input> 不会自动聚焦。
解决方法必须手动补链:
- 确保子组件宿主(如
<my-input></my-input>)本身可聚焦:加tabindex="0"或用focusable属性 - 子组件 Shadow 内要有首个可聚焦节点(
<input>、<button></button>等),且未被disabled或display: none - 在子组件的
focusin事件里调用shadowRoot.querySelector('input').focus(),完成二次跳转 - 避免在
focusin中反复调用focus(),否则 Safari 可能触发无限循环
slot 分发失效,嵌套内容不渲染?
常见现象是外层组件写了 <my-panel><my-button>Click</my-button></my-panel>,但按钮没显示——不是 Shadow DOM 不支持嵌套,而是漏了 <slot></slot> 声明。
<slot></slot> 是显式契约,不是自动转发:
- 外层组件 Shadow 内必须有
<slot></slot>标签,否则子节点留在 Light DOM 里,视觉上“消失”但 DOM 仍存在 - 如果子组件是具名 slot(如
<slot name="actions"></slot>),外层必须用<my-button slot="actions"></my-button>显式指定 - 未命名
<slot></slot>是兜底,但只接收“未被其他 slot 消费”的节点;多个具名 slot 共存时,不带slot属性的子节点会被丢弃 - 嵌套层级越深,越要检查每一层是否都配了对应
<slot></slot>,缺一层就断链
样式穿透失败,父主题色影响不到子组件?
CSS 自定义属性(--theme-color)和可继承属性(color、font-family)默认能穿透 Shadow Boundary,但容易踩坑:
-
:host必须显式声明变量,例如:host { --theme-color: blue; },否则子 Shadow 里var(--theme-color)读不到 - 子组件不能依赖父组件的 class 名去继承样式(
.dark-theme p在子 Shadow 内无效),只能靠变量或继承属性 - 若想彻底隔离字体继承,需在子组件
:host或:host *上重置,如font-size: inherit;,而非all: initial(会清掉所有继承) - 注意 Safari 对
inherit在多层 Shadow 中的处理略有延迟,建议用font: inherit保证一致性
事件冒泡到外层后 target 错乱?
Shadow 内部触发的 click 事件冒泡穿出 Boundary 后,event.target 会被重定向为宿主元素,原始目标丢失。
要拿到真实点击节点,必须用:
-
event.composedPath()[0]—— 返回事件路径中第一个实际被点击的节点(含 Shadow 内部节点) - 不要依赖
event.target或event.srcElement,它们在跨 Boundary 后已失真 - 如果监听的是外层容器,又需要知道具体点中了哪个子组件内部按钮,就得结合
composedPath()和节点类型判断 - 注意
composedPath()在 Firefox 和 Chrome 中行为一致,但旧版 Safari 需 polyfill
嵌套越深,越要警惕 Shadow Boundary 对 DOM 查询、事件路径和样式继承的隐性干预——它不阻止嵌套,但每层都得主动声明契约,而不是假设“写了就能通”。











