Shadow DOM 中 body 或 .app-container 选择器无效,因 CSS 匹配不跨边界;::part() 是唯一标准穿透方式,需组件显式声明 part 并 exportparts;CSS 变量靠继承跨边界,但必须挂载在宿主元素上;::slotted() 仅支持可继承属性,禁用布局类样式。

Shadow DOM 里写 body 或 .app-container 为什么没用
因为浏览器在解析 Shadow DOM 内部的 <style> 时,压根不把 body、header、.app-container 这类外部选择器纳入匹配流程——它们既不是宿主元素,也不在当前 shadow tree 的作用域内。规则直接被跳过,连计算权重的机会都没有。
常见误操作包括:
- 在 shadow 内部
<style>中写button { color: red },以为能覆盖外部按钮样式(实际只影响自己内部的<button>) - 用
!important强行加权(无效,跨 boundary 不参与同一 CSSOM) - 往
document.head动态插入<style>(污染全局,违背封装)
::part() 是唯一标准支持的穿透方式
::part() 是目前唯一被 W3C 标准支持、且主流浏览器(Chrome 90+、Firefox 100+、Safari 16.4+)稳定实现的穿透机制,但它有严格前提:
- 组件内部节点必须显式声明
part="submit"属性 - 宿主元素需通过
exportparts="submit"主动暴露该 part 名 - 外部样式只能写成
my-button::part(submit),大小写必须完全一致 -
::part()不支持伪类(如::part(submit):hover无效),状态响应要靠:host([disabled])配合内部规则
var(--x) 能“透入”不是因为穿透,而是继承链天然跨 boundary
CSS 变量生效靠的是继承查找机制:从当前元素出发,沿 DOM 树向上找定义,这个过程天然跨过 Shadow Boundary。但变量必须挂在宿主元素自身或其直系祖先上才有效。
立即学习“前端免费学习笔记(深入)”;
- ✅ 正确:
<my-button style="--primary: #3b82f6;"></my-button>或<my-button class="theme-dark"></my-button>+.theme-dark { --primary: #1e1e1e; } - ❌ 无效:
<div class="wrapper"><my-button></my-button></div>+.wrapper { --primary: red; }(变量没挂到 host,继承链在此中断) - JS 动态设置也一样:
element.style.setProperty('--bg', '#2d2d2d')才管用,document.body.style.setProperty没用 - 所有
var(--x)都必须带 fallback,比如background: var(--bg, #fff);,否则变量未定义时会回退到初始值
::slotted() 只能设可继承属性,别碰布局
::slotted() 是唯一能影响 slot 分发内容的伪类,但它被浏览器严格限制:仅允许设置 color、font、line-height、background-color 等可继承属性。布局类属性一律被忽略。
- ::slotted(*) { margin: 16px; } —— 完全无效,DevTools 里都看不到这条声明被应用
- ::slotted(p) { color: var(--text-color); } —— 有效,但只作用于传入 slot 的顶层
<p>元素 - 根本原因:slot 分发的是 light DOM 节点的引用,不是副本;若允许修改布局,就等于打破封装边界,浏览器明确禁止
exportparts 必须逐层显式声明,而 ::slotted() 对布局属性的限制不是 bug,是封装边界的强制保障。



















