CSS Shadow Parts 模块 Level 1

W3C 工作草案

关于本文档的更多细节
此版本:
https://www.w3.org/TR/2025/WD-css-shadow-parts-1-20251216/
最新发布版本:
https://www.w3.org/TR/css-shadow-parts-1/
编辑草案:
https://drafts.csswg.org/css-shadow-parts/
历史:
https://www.w3.org/standards/history/css-shadow-parts-1/
反馈:
CSSWG 议题 仓库
规范内嵌
编辑:
Tab Atkins-Bittner (Google)
(Google)
为此规范建议编辑:
GitHub 编辑器
测试套件:
https://wpt.fyi/results/css/css-shadow-parts/

摘要

本规范定义了 ::part() 伪元素,用于影子 宿主,允许 影子宿主选择性地将其 影子树中的选定元素暴露给外部页面,以用于样式设置。

CSS 是一种用于描述结构化文档 (如 HTML 和 XML) 在屏幕、纸张等媒介上的渲染的语言。

本文档状态

本节描述本文档在发布时的状态。 当前 W3C 出版物列表 以及本技术报告的最新修订版 可在 W3C 标准和草案索引中找到。

本文档由 CSS 工作组作为 工作草案 发布,并使用推荐标准 轨道。 作为工作草案发布 并不表示获得 W3C 及其成员的认可。

本文档是一份草案文档, 并且可能在任何时候被其他文档更新、替换 或废弃。 除非作为进行中的工作,否则不宜引用本文档。

请通过在 GitHub 中提交议题(首选) 发送反馈, 并在标题中包含规范代码 “css-shadow-parts”,如下所示: “[css-shadow-parts] …评论摘要…”。 所有议题和评论都会被归档。 或者,反馈也可以发送到(已归档的)公共邮件列表 www-style@w3.org

本文档受 2025 年 8 月 18 日 W3C 流程文档管辖。

本文档由一个依据 W3C 专利政策运行的小组制作。 W3C 维护了一份与该小组交付物相关的任何公开专利 披露列表; 该页面还包含披露专利的说明。 任何实际知晓某项专利,并认为该专利 包含必要权利要求的个人, 都必须按照 W3C 专利政策第 6 节披露该信息。

1. 简介

Shadow DOM 允许作者将其页面划分为“组件”, 即一些标记子树,其细节只与组件本身相关, 而与外部页面无关。 这降低了用于页面某一部分的样式 意外过度应用并使页面另一部分显示错误的可能性。 然而,这种样式屏障也使页面在实际想要与组件交互时 更难与其组件交互。

本规范定义了 ::part() 伪元素, 它允许作者从外部页面的上下文中,对 影子 树中有意暴露的特定元素进行样式设置。 结合自定义属性, 外部页面可以将特定值 (如主题颜色) 传入组件,由组件自行处理, 这些伪元素允许组件与外部页面 以安全而强大的方式交互, 在不放弃所有控制的情况下 保持封装。

测试

shadow parts 的通用测试


1.1. 动机

为了让自定义元素完全有用,并与内置元素一样有能力, 应该可以从外部对它们的部分进行样式设置。 究竟哪些内容可以从外部设置样式,应由元素作者控制。 此外,自定义元素应该能够为样式设置提供一个稳定的“API”。 也就是说,用于设置自定义元素某一部分样式的选择器 不应暴露或要求了解该元素的内部细节。 自定义元素作者应该能够更改元素的内部细节, 同时保持选择器不变。

先前提出的用于对影子树内部进行样式设置的方法, 即 >>> 组合器, 结果证明过于强大, 它将组件过多的内部结构暴露给审查, 破坏了使用 Shadow DOM 带来的一些封装好处。 因此, 以及其他与性能相关的原因, >>> 组合器最终被移除。

这使我们只能使用自定义属性作为对影子树内部设置样式的唯一方式: 组件会声明它使用某些自定义 属性来设置其内部样式, 然后外部页面可以根据需要在影子 宿主上设置这些属性, 让继承把这些值传递到需要它们的位置。 这对于许多简单的主题化用例非常有效。

然而,在某些情况下,这种方式会失效。 如果组件希望允许对其影子树中的某个内容进行任意样式设置, 唯一的方法就是定义数百个自定义 属性(每个希望允许控制的 CSS 属性一个), 这显然在可用性和性能方面 都很荒谬。 如果作者还希望根据 :hover 等伪类 以不同方式设置组件样式, 情况会更加复杂; 组件需要为每个伪类 (以及每种组合, 如 :hover:focus, 导致组合爆炸) 复制所使用的自定义属性。 这会进一步加剧可用性和性能问题。

我们引入 ::part(),以更优雅且性能更好的方式处理这种情况。 与其把所有内容都塞进自定义 属性名称中, 该功能存在于选择器和样式规则语法中, 就像它本该如此。 这对于组件作者 和组件用户都更可用, 应该具有更好的性能, 并允许更好的封装/API 表面。

需要注意的是,::part() 绝对没有提供新的理论能力。 它不是 >>> 组合器的翻版, 它只是对作者已经可以通过自定义 属性做到的事情,提供了一种更方便、更一致的语法。 通过将元素中显式“发布”的部分 (part element map) 与它恰好包含的子部分分离, 它也有助于封装, 因为作者可以使用 ::part(),而不必担心 意外的过度样式设置。

2. 暴露影子元素:

影子树中的元素可以使用 part 和 exportparts 属性, 导出给树外部的样式表进行样式设置。

每个元素都有一个part 名称列表,它是一个 token 的有序集合

每个元素都有一个转发 part 名称列表,它是一个列表,其中包含若干元组,每个元组包含一个字符串表示被转发的 内部 part, 以及一个字符串给出它将被暴露成的名称。

每个影子根都可以被认为具有一个part 元素映射,其键是字符串,值是元素的有序 集合

part 元素映射仅作为本规范中计算样式算法的一部分进行描述。 它不会通过 DOM 暴露, 因为计算它可能开销很大, 并且暴露它可能允许访问闭合影子根内部的元素。

Part 元素映射会受到元素的添加和移除, 以及 DOM 中元素的part 名称列表转发 part 名称列表变化的影响。

计算 影子根 outerRootpart 元素映射
  1. 对于 outerRoot 内的每个后代 el

    1. 对于 elpart 名称 列表中的每个 name,将 el 追加outerRootpart 元素映射[name]。

    2. 如果 el 本身是一个影子宿主, 则令 innerRoot 为其影子根。

    3. 计算 innerRootpart 元素映射

    4. 对于 el转发 part 名称列表中的每个 innerName/outerName

      1. 如果 innerName 是一个 ident:

        1. innerPartsinnerRootpart 元素 映射[innerName]

        2. innerParts 中的元素追加outerRootpart 元素 映射[outerName]

      2. 如果 innerName 是一个伪元素名称:

        1. innerRoot 中具有该名称的伪元素追加outerRootpart 元素 映射[outerName]。

2.1. 命名影子元素: part 属性

影子树中的任何元素都可以具有一个 part 属性。 该属性用于将该元素暴露到影子 树外部。

测试

part 属性被解析为一个由空格分隔的 token 列表,表示此元素的 part 名称。

注: 可以给一个 part 多个名称。 “part name” 应被视为类似于 class, 而不是 id 或 tagname。

<style>
  c-e::part(textspan) { color: red; }
</style>

<template id="c-e-template">
  <span part="textspan">这段文本将是红色</span>
</template>
<c-e></c-e>
<script>
  // 将模板添加为自定义元素 c-e
  ...
</script>

2.2. 转发影子元素: exportparts 属性

影子树中的任何元素都可以具有一个 exportparts 属性。 如果该元素是影子宿主, 则该属性用于允许由此影子树外部的规则 对 影子 树内部宿主中的 part 进行样式设置(就像它们是与宿主在同一树中、 由 part 属性命名的元素一样)。
测试

exportparts 属性被解析为一个由逗号分隔的 part 映射列表。 每个 part 映射是以下之一:

innerIdent : outerIdent

innerIdent/outerIdent 添加到 el 的转发 part 名称列表

ident

ident/ident 添加到 el 的转发 part 名称列表

注: 这是 ident : ident 的简写。

::ident : outerIdent

如果 ::ident完全 可样式化伪元素的名称, 则将 ::ident/outerIdent 添加到 el 的转发 part 名称列表。 否则,不执行任何操作。

其他任何内容

为错误恢复/未来兼容性而忽略。

注: 可以将一个子 part 映射到多个名称。

<style>
  c-e::part(textspan) { color: red; }
</style>

<template id="c-e-outer-template">
  <c-e-inner exportparts="innerspan: textspan"></c-e-inner>
</template>

<template id="c-e-inner-template">
  <span part="innerspan">
    这段文本将是红色,因为包含它的 shadow
    host 将 innerspan 转发到文档中,名称为 "textspan",
    并且文档样式匹配它。
  </span>
  <span part="textspan">
    这段文本不会是红色,因为文档样式中的 textspan
    无法匹配内部自定义元素里的 part,
    如果它没有被转发的话。
  </span>
</template>

<c-e></c-e>
<script>
  // 将模板添加为自定义元素 c-e-inner、c-e-outer
  ...
</script>
例如,一个完全可样式化 伪元素可以在 exportparts 属性中使用, 以伪装成其所在组件的 ::part()
<template id=custom-element-template>
  <p exportparts="::before : preceding-text, ::after : following-text">
    Main text.
</template>

使用该模板的元素 可以使用类似 x-component::part(preceding-text) 的选择器来定位其影子中的 p::before 伪元素, 因此组件用户不需要知道 前置文本是作为伪元素实现的。

3. 选择影子元素:::part() 伪元素

::part() 伪元素 允许你选择通过 part 属性暴露出来的元素。 其语法为:

::part() = ::part( <ident>+ )
测试

::part() 伪元素只有在原始元素影子 宿主时,才会匹配任何内容。

例如, 如果你有一个自定义按钮, 其中包含一个为样式设置而暴露的 "label" 元素 (通过 part="label"), 你可以用 x-button::part(label) 选中它。
Part 名称的行为类似于类: 多个元素可以具有相同的 part 名称, 单个元素也可以具有多个 part 名称。

一个选项卡条控件可能有多个 带有 part="tab" 的元素, 它们都会被 ::part(tab) 选中。

如果一次只有一个选项卡处于活动状态, 可以用 part="tab active" 对其进行特殊标记,然后由 ::part(tab active)(或 ::part(active tab),因为顺序 无关紧要)选中。

::part() 伪元素是一个完全可样式化 伪元素。 如果原始元素影子 根part 元素映射包含指定的 <ident>::part() 表示以该 ident 为键的元素; 如果提供了多个 idents,并且 part 元素 映射包含它们全部, 它表示以每个 ident 为键的元素的交集。 否则,它不匹配任何内容。

::part() 伪元素根据其在原始元素影子树中的位置 进行继承。

例如,x-panel::part(confirm-button)::part(label) 永远不会匹配任何内容。 这是因为这样做会暴露比预期更多的结构信息。

如果 <x-panel> 的内部确认按钮使用了类似 part="label => confirm-label" 的内容,将按钮的内部 part 向上转发到面板自己的 part 元素映射中, 那么类似 x-panel::part(confirm-label) 的选择器就只会选择该一个 按钮的 label, 而忽略任何其他 label。

4. Element 接口的扩展

partial interface Element {
  [SameObject, PutForwards=value] readonly attribute DOMTokenList part;
};
测试

part 属性的 getter 必须返回一个 DOMTokenList 对象, 其关联元素是上下文对象, 且其关联属性的本地名称是 part。 这个特定 DOMTokenList 对象的 token 集也称为元素的 parts。

在 DOM 规范中将其定义为 superglobal。 [w3c/csswg-drafts Issue #3424]

5. 用于解析的微语法

5.1. 解析 part 映射的规则

有效 part 映射 是一个由 token 组成的元组, 这些 token 由 U+003A COLON 字符分隔, 且 U+003A COLON 前后可以有任意数量的空格字符。 token 不得包含 U+003A COLON 或 U+002C COMMA 字符。

解析 part 映射的规则如下:

  1. input 为正在解析的字符串。

  2. position 为指向 input 的指针,最初指向该 字符串的开头。

  3. 收集一串码点,这些码点是 空格字符

  4. 收集一串码点,这些码点不是 空格字符或 U+003A COLON 字符, 并令 first token 为结果。

  5. 如果 first token 为空,则返回 error。

  6. 收集一串码点,这些码点是 空格字符。

  7. 如果已经到达 input 的末尾,则返回元组first token, first token

  8. 如果 position 处的字符不是 U+003A COLON 字符,则返回 error。

  9. 消耗 U+003A COLON 字符。

  10. 收集一串码点,这些码点是 空格字符。

  11. 收集一串码点,这些码点不是 空格字符或 U+003A COLON 字符。 并令 second token 为结果。

  12. 如果 second token 为空,则返回 error。

  13. 收集一串码点,这些码点是 空格字符。

  14. 如果 position 没有越过 input 的末尾,则返回 error。

  15. 返回元组first token, second token)。

5.2. 解析 part 映射列表的规则

有效 part 映射列表是若干有效 part 映射, 由 U+002C COMMA 字符分隔, 且 U+002C COMMA 前后可以有任意数量的空格字符

解析 part 映射列表的规则如下:

  1. input 为正在解析的字符串。

  2. 按逗号拆分字符串 input。 令 unparsed mappings 为由此产生的字符串列表。

  3. mappings 为一个初始为空的列表,其中包含 token 的元组。 这个列表将是此算法的结果。

  4. 对于 unparsed mappings 中的每个字符串 unparsed mapping, 运行以下子步骤:

    1. 如果 unparsed mapping 为空或仅包含空格字符, 则继续循环的下一次迭代。

    2. mapping 为使用 解析 part 映射的规则解析 unparsed mapping 的结果。

    3. 如果 mapping 是 error,则继续循环的下一次迭代。 这允许客户端跳过无法理解的新语法。

    4. mapping 追加到 mappings

6. 隐私注意事项

本规范定义了定位页面上元素样式的新方式, 这些元素已经可以通过其他方式完全设置样式。 因此,它不会引入新的隐私注意事项。

7. 安全注意事项

由于影子树有意不是安全边界, 而只是为页面作者提供便利, 以这种方式将其暴露给选择器不会引入新的安全注意事项。

8. 变更

自 2018 年 11 月 15 日的首次公开工作 草案以来的变更

一致性

文档约定

一致性要求通过描述性断言 与 RFC 2119 术语的组合来表达。本文档规范性部分中的关键词 “MUST”、 “MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、 “RECOMMENDED”、“MAY” 和 “OPTIONAL” 应按 RFC 2119 中的描述进行解释。 但是,为了可读性,本规范中这些词并不都以全大写 字母出现。

本规范的所有文本都是规范性的,但明确标记为 非规范性的章节、示例和注释除外。 [RFC2119]

本规范中的示例以 “for example” 这些词引入, 或者通过 class="example" 与规范性文本分隔开, 如下所示:

这是一个资料性示例的例子。

资料性注释以 “Note” 一词开头,并通过 class="note" 与规范性文本分隔开,如下所示:

Note,这是一个资料性注释。

建议是经过样式化以引起特别注意的规范性章节,并通过 <strong class="advisement"> 与其他规范性文本分隔开,如 下所示: 用户代理 MUST 提供一个可访问的替代方案。

测试

与本规范内容相关的测试 可以记录在像这样的“测试”块中。 任何此类块都是非规范性的。


一致性类别

对本规范的一致性 是针对三种一致性类别定义的:

样式表
一个 CSS 样式表
渲染器
一个解释样式表语义并渲染 使用这些样式表的文档的 UA
创作工具
一个编写样式表的 UA

如果样式表中所有使用本模块定义语法的语句, 都根据通用 CSS 语法以及本模块定义的每个 特性的各自语法是有效的, 则该样式表符合本规范。

如果渲染器在按照相应规范解释样式表之外, 还通过正确解析本规范定义的所有特性 并相应地渲染文档来支持这些特性, 则该渲染器符合本规范。但是,UA 由于设备限制而无法正确渲染文档, 并不会使该 UA 不符合规范。(例如,UA 不要求在单色显示器上渲染颜色。)

如果创作工具编写的样式表在语法上符合 通用 CSS 语法以及本模块中每个特性的各自语法, 并且满足本模块中所述样式表的所有其他一致性要求, 则该创作工具符合本规范。

部分实现

为了让作者能够利用向前兼容的解析规则来 指定回退值,CSS 渲染器必须将任何没有可用支持级别的 @ 规则、属性、属性值、关键词 和其他语法构造视为无效(并酌情忽略)。特别是,用户代理不得在单个 多值属性声明中选择性地忽略不支持的组件值而保留受支持的值: 如果任何值被视为无效 (不支持的值必须如此),CSS 要求忽略整个声明。

不稳定和 专有特性的实现

为避免与未来稳定的 CSS 特性发生冲突, CSSWG 建议在实现 CSS 的不稳定特性和专有扩展时,遵循最佳实践

非实验性实现

一旦规范达到候选推荐阶段, 非实验性实现就成为可能,并且实现者应该 发布任何 CR 级特性的无前缀实现,只要他们 能够证明该实现已根据规范正确实现。

为建立并维护 CSS 在各实现之间的互操作性, CSS 工作组要求非实验性 CSS 渲染器在发布任何 CSS 特性的无前缀实现之前, 向 W3C 提交实现报告(以及在必要时, 提交用于该实现报告的测试用例)。 提交给 W3C 的测试用例需经 CSS 工作组审查和修正。

关于提交测试用例和实现报告的更多信息, 可从 CSS 工作组网站 https://www.w3.org/Style/CSS/Test/ 获取。 问题应发送到 public-css-testsuite@w3.org 邮件 列表。

索引

由本 规范定义的术语

由引用定义的 术语

参考文献

规范性参考文献

[CSS-PSEUDO-4]
Elika Etemad; Alan Stearns. CSS Pseudo-Elements Module Level 4. 27 June 2025. WD. URL: https://www.w3.org/TR/css-pseudo-4/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. 12 March 2024. WD. URL: https://www.w3.org/TR/css-values-4/
[CSS-VARIABLES-1]
Tab Atkins Jr.. CSS Custom Properties for Cascading Variables Module Level 1. 16 June 2022. CR. URL: https://www.w3.org/TR/css-variables-1/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4. 11 November 2022. WD. URL: https://www.w3.org/TR/selectors-4/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL 索引

partial interface Element {
  [SameObject, PutForwards=value] readonly attribute DOMTokenList part;
};

议题索引

在 DOM 规范中将其定义为 superglobal。 [w3c/csswg-drafts Issue #3424]