Skip to content

Commit bcfb500

Browse files
committed
Per Paris resolution, add Shadow Parts as ED.
1 parent d714a9b commit bcfb500

2 files changed

Lines changed: 952 additions & 0 deletions

File tree

css-shadow-parts/index.bs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<pre class='metadata'>
2+
Title: CSS Shadow Parts
3+
Shortname: css-shadow-parts
4+
Level: 1
5+
Group: CSSWG
6+
Status: ED
7+
Work Status: exploring
8+
URL: http://drafts.csswg.org/css-shadow-parts/
9+
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/
10+
Abstract: This specification defines the ''::part()'' and ''::theme()'' pseudo-elements on <a>shadow hosts</a>, allowing <a>shadow hosts</a> to selectively expose chosen elements from their <a>shadow tree</a> to the outside page for styling purposes.
11+
Warning: not ready
12+
</pre>
13+
14+
<pre class=link-defaults>
15+
spec:selectors-4;
16+
type:selector; text::hover
17+
type:dfn; text:dynamic profile
18+
type:dfn; text:structural pseudo-class
19+
spec:dom; type:dfn; for:/; text:shadow root
20+
</pre>
21+
22+
Introduction {#intro}
23+
=====================
24+
25+
Issue: This spec is intentionally a rough sketch at the moment.
26+
It should contain all the details necessary to evaluate the proposal,
27+
but is intentionally avoiding precise algorithms at the moment,
28+
to aid in easy comprehension
29+
and to, hopefully, discourage implementation from this sketch.
30+
31+
Shadow DOM allows authors to separate their page into "components",
32+
subtrees of markup whose details are only relevant to the component itself,
33+
not the outside page.
34+
This reduces the chance of a style meant for one part of the page
35+
accidentally over-applying and making a different part of the page look wrong.
36+
However, this styling barrier also makes it harder for a page to interact with its components
37+
when it actually <em>wants</em> to do so.
38+
39+
This specification defines the ''::part()'' and ''::theme()'' pseudo-elements,
40+
which allow an author to style specific, purposely exposed elements in a <a>shadow tree</a>
41+
from the outside page's context.
42+
In combination with <a>custom properties</a>,
43+
which let the outside page pass particular values
44+
(such as theme colors)
45+
into the component for it to do with as it will,
46+
these pseudo-elements allow components and the outside page
47+
to interact in safe, powerful ways,
48+
maintaining encapsulation
49+
without surrending all control.
50+
51+
Motivation {#motivation}
52+
------------------------
53+
54+
For obvious reasons,
55+
it's valuable to let the outside page style the internals of a shadow tree,
56+
at least in some limited ways.
57+
(The ubiquity of UA-specific pseudo-elements for the various input elements shows this.)
58+
59+
The previous proposed method for doing so,
60+
the >>> combinator,
61+
turned out to be <em>too powerful</em> for its own good;
62+
it exposed too much of a component's internal structure to scrutiny,
63+
defeating some of the encapsulation benefits that using Shadow DOM brings.
64+
For this,
65+
and other performance-related reasons,
66+
the >>> combinator was eventually removed from the <a>dynamic profile</a>.
67+
68+
This left us with using <a>custom properties</a> as the only way to style into a shadow tree:
69+
the component would advertise that it uses certain <a>custom properties</a> to style its internals,
70+
and the outer page could then set those properties as it wished on the <a>shadow host</a>,
71+
letting inheritance push the values down to where they were needed.
72+
This works very well for many simple theming use-cases.
73+
74+
However, there are some cases where this falls down.
75+
If a component wishes to allow arbitrary styling of something in its shadow tree,
76+
the only way to do so is to define hundreds of <a>custom properties</a>
77+
(one per CSS property they wish to allow control of),
78+
which is obviously ridiculous
79+
for both usability and performance reasons.
80+
The situation is compounded if authors wish to style the component differently
81+
based on pseudo-classes like '':hover'';
82+
the component needs to duplicate the <a>custom properties</a> used
83+
for each pseudo-class
84+
(and each combination,
85+
like '':hover:focus'',
86+
resulting in a combinatorial explosion).
87+
This makes the usability and performance problems even worse.
88+
89+
We introduce ''::part()'' to handle this case much more elegantly and performantly.
90+
Rather than bundling everything into <a>custom property</a> names,
91+
the functionality lives in selectors and style rule syntax,
92+
like it's meant to.
93+
This is far more usable for both component authors
94+
and component users,
95+
should have much better performance,
96+
and allows for better encapsulation/API surface.
97+
98+
Another interesting facet of using <a>custom properties</a>,
99+
however,
100+
is that inheritance doesn't stop at the first shadow tree.
101+
Unless explicitly blocked,
102+
a <a>custom property</a> inherits down thru nested trees,
103+
allowing authors to style deeply nested components
104+
as easily as they style directly-visible ones.
105+
The same considerations apply to this case,
106+
so we introduce ''::theme()'' to handle this.
107+
108+
It's important to note that ''::part()'' and ''::theme()''
109+
offer <em>absolutely zero new theoretical power</em>.
110+
They are not a rehash of the ''>>>'' combinator,
111+
they're simply a more convenient and consistent syntax
112+
for something authors can already do with <a>custom properties</a>.
113+
By separating out the explicitly "published" parts of an element
114+
(the <a>shadow part map</a>
115+
from the sub-parts that it merely happens to contain
116+
(the <a>shadow theme map</a>,
117+
it also helps with encapsulation,
118+
as authors can use ''::part()'' without fear of accidental over-styling.
119+
120+
121+
Exposing a Shadow Element: the <{html-global/part}> attribute {#part-attr}
122+
=============================================================
123+
124+
Any element in a shadow tree can have a <dfn element-attr for=html-global>part</dfn> attribute.
125+
This is used to expose the element outside the <a>shadow tree</a>,
126+
and to "forward" sub-parts of the element
127+
(if it has its own <a>shadow tree</a>)
128+
to outside the <a>shadow tree</a>.
129+
130+
The part attribute is parsed as a comma-separated list of part mappings.
131+
Each part mapping is one of:
132+
133+
<dl class=switch>
134+
: <code>ident</code>
135+
:: Adds «[ ident → el ]» to the <a>shadow root's</a> <a>shadow part map</a>.
136+
137+
: <code>ident1 => ident2</code>
138+
:: If el is a <a>shadow host</a>,
139+
and it's <a>shadow root's</a> <a>shadow part map</a> |partMap| [=map/contains=] ident1,
140+
then this adds «[ ident2 → |partMap|[ident1] ]» to the <a>shadow root's</a> <a>shadow part map</a>.
141+
142+
: <code>* => prefix*</code>
143+
:: If el is a <a>shadow host</a>,
144+
then [=map/for each=] |ident| → |subEl| in el's <a>shadow root's</a> <a>shadow part map</a>,
145+
«[ prefix + |ident| → |subEl| ]» is added to the <a>shadow root's</a> <a>shadow part map</a>.
146+
147+
: anything else
148+
:: Ignored for error-recovery / future compat.
149+
</dl>
150+
151+
Note: It's okay to give a part multiple names,
152+
or map a sub-part to several names.
153+
The "part name" should be considered similar to a class,
154+
not an id or tagname.
155+
156+
Each <a>shadow root</a> has a <dfn export for="shadow root">shadow part map</dfn>
157+
and a <dfn export for="shadow root">shadow theme map</dfn>,
158+
both of which are <a>ordered maps</a>.
159+
160+
The <a>shadow part map</a> contains all the entries described by the elements in its <a>shadow tree</a>,
161+
as described above.
162+
163+
If the <a>shadow root</a>'s {{ShadowRoot/mode}} is {{ShadowRootMode/"closed"}},
164+
the <a>shadow theme map</a> is identical to the <a>shadow part map</a>.
165+
Otherwise,
166+
it's the concatenation of the <a>shadow part map</a>
167+
with the <a>shadow theme maps</a> of every <a>shadow host</a>'s <a>shadow root</a> in its <a>shadow tree</a>.
168+
169+
Issue: TODO: Define a syntax or new attribute
170+
to prevent an element from adding things to its <a>shadow root's</a> <a>shadow theme map</a>.
171+
172+
Selecting a Shadow Element: the ''::part()'' and ''::theme()'' pseudo-elements {#part-theme}
173+
============================================================================================
174+
175+
The <dfn selector>::part()</dfn> and <dfn selector>::theme()</dfn> pseudo-elements
176+
(collectively, the <dfn export>shadow-part pseudo-elements</dfn>)
177+
allow you to select elements that have been exposed via a <{html-global/part}> attribute.
178+
The syntaxes of them are:
179+
180+
<pre class=prod>
181+
::part() = ::part( <<ident>> )
182+
::theme() = ::theme( <<ident>> )
183+
</pre>
184+
185+
The ''::part()'' pseudo-element only matches anything
186+
when the <a>originating element</a> is a <a>shadow host</a>.
187+
If the <a>originating element's</a> <a>shadow root's</a> <a>shadow part map</a>
188+
[=map/contains=] the specified <<ident>>,
189+
''::part()'' matches the element or elements keyed to that <<ident>>.
190+
Otherwise, it matches nothing.
191+
192+
<div class="example">
193+
For example,
194+
if you have a custom button
195+
that contains a "label" element that is exposed for styling
196+
(via <code>part="label"</code>),
197+
you can select it with
198+
''#the-button::part(label)''.
199+
</div>
200+
201+
The ''::theme()'' pseudo-element is similar,
202+
except it can match regardless of whether the <a>originating element</a>
203+
is a <a>shadow host</a> or not.
204+
It matches the elements keyed to the specified <<ident>>
205+
in the <a>shadow theme map</a> of the <a>shadow trees</a>
206+
of the <a>originating element</a> or any descendants.
207+
208+
<div class="example">
209+
For example,
210+
'':root::theme(label)'' matches any element with <code>part="label"</code>
211+
anywhere in the entire document,
212+
no matter how deeply nested into shadow trees they are.
213+
</div>
214+
215+
The <a>shadow-part pseudo-elements</a> can take additional pseudo-classes after them,
216+
such as ''x-button::part(label):hover'',
217+
but never match the <a>structural pseudo-classes</a>
218+
or any other pseudo-classes that match based on tree information
219+
rather than local element information.
220+
221+
The <a>shadow-part pseudo-elements</a> also can take additional pseudo-elements after them,
222+
such as ''x-button::part(label)::before'',
223+
but never match additional <a>shadow-part pseudo-elements</a>.
224+
225+
<div class=example>
226+
For example,
227+
''x-panel::part(confirm-button)::part(label)''
228+
never matches anything.
229+
This is because doing so would expose more structural information
230+
than is intended.
231+
232+
One can still target the nested label with a selector like
233+
''x-panel::theme(label)''.
234+
However, this will also select the labels of any other buttons in the panel.
235+
236+
If the <code>&lt;x-panel></code>'s internal confirm button had used something like
237+
<code>part="confirm-button, * => confirm-*"</code>
238+
to forward the button's internal parts up into the panel's own <a>shadow part map</a>,
239+
then a selector like
240+
''x-panel::part(confirm-label)''
241+
would select just the one button's label,
242+
ignoring any other labels.
243+
</div>

0 commit comments

Comments
 (0)