CSS Parts are now supported in most modern browsers, so this is a brief write up on what they're useful for and how to use them.
Shadow DOM & styling
Let's say we have an example-button
component, used like so:
<example-button>Some text</example-button>
Its inner shadow DOM may look like this:
<style>
:host {
display: inline-block;
}
button {
color: hotpink;
}
</style>
<button>
<slot></slot>
</button>
This is a fairly atomic component in that it is fairly low level and can't be split up into further components.
In this case, we can style the example-button
element itself through regular CSS selectors:
example-button {
background: cyan;
margin: 1em;
}
However, what if we want to change the text colour? You see we wanted a default colour of hotpink
but this now means we can't override it from outside.
This is solved through CSS custom properties:
button {
color: var(--example-button-colour, hotpink);
}
Now it will default to hotpink
but allow us to override it like so:
example-button {
--example-button-color: green;
}
This solves most cases where we want to give consumers the ability to style some of our component's internals.
Slightly more complex components
Now let's assume we have a more complex component, like a card:
<example-card heading="My heading">
<p>My contents...</p>
</example-card>
With a shadow DOM like so:
<div class="container">
<div class="heading">
${this.heading}
</div>
<div class="content">
<slot></slot>
</div>
</div>
The heading could have been a slot here, but that isn't always the case, so let's assume it couldn't be this time.
If we wanted to allow the consumer to style the heading, we could quickly end up with some CSS like this:
.heading {
margin: var(--example-card-heading-margin);
color: var(--example-card-heading-colour);
padding: var(--example-card-heading-padding);
}
Of course, this becomes a bit of a pain.
The old way - @apply
One solution to this need for blocks of CSS rules already existed long ago when web components were still being finalised: the @apply
rule.
It looked like this:
.heading {
@apply --example-card-heading;
}
Which was set like this:
example-card {
--example-card-heading: {
margin: .2em;
padding: .2em;
color: hotpink;
}
}
This tidied up those edge cases where people wanted to set several CSS properties at once, but it also opened the flood gates and made a bit of a mess (and lots of debates) so was ultimately deprecated and dropped.
The new way - ::part()
The modern solution, now accepted in all modern browsers, is ::part()
.
Similar to how slots work, an element is given a part
and CSS rules can reference that part.
Our card from earlier would have a shadow DOM like so:
<div class="container">
<div class="heading" part="heading">
${this.heading}
</div>
<div class="content" part="content">
<slot></slot>
</div>
</div>
Which means you can style it via ::part()
:
example-card::part(heading) {
padding: .2em;
margin: .2em;
color: hotpink;
}
You can even use pseudo-selectors after this:
example-card::part(heading):hover {
However, you cannot select a part within a part:
example-card::part(heading)::part(another-part) {
Forwarding parts
If you have nested components, you may want to expose an inner component's parts via your own component.
Let's say you have a DOM tree like so:
<root-element>
#shadow-root (open)
<child-element>
#shadow-root (open)
<div part="childpart"></div>
</child-element>
</root-element>
If you want to expose childpart
to consumers, you can use exportparts
:
<child-element exportparts="childpart: rootpart">
This means your root-element
will be exposing a part named rootpart
.
The syntax here is essentially inner-name: outer-name
and is a comma-separated list so you can expose multiple parts.
Wrap up
This is all cool stuff, but you should still carefully consider whether your styling really should be exposed or not.
If a page has been architected well, I would imagine most of the components are fairly atomic and wouldn't need to expose so much of their styling to the consumer. If you make good use of slots and CSS variables, you probably shouldn't need parts so often.
Where it could be useful is if you had a complex, high level component like an app shell or a data grid. In these cases you may need to expose a lot of styling.
Top comments (0)