Using CSS shadow parts in web components
Tags: webcomponents, css, litelement, polymer
Published: 2021-03-20 12:45:37

[CSS Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) 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:

```html
<example-button>Some text</example-button>
```

Its inner shadow DOM may look like this:

```html
<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:

```css
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:

```css
button {
  color: var(--example-button-colour, hotpink);
}
```

Now it will default to `hotpink` but allow us to override it like so:

```css
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:

```html
<example-card heading="My heading">
  <p>My contents...</p>
</example-card>
```

With a shadow DOM like so:

```html
<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:

```css
.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:

```css
.heading {
  @apply --example-card-heading;
}
```

Which was set like this:

```css
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:

```html
<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()`:

```css
example-card::part(heading) {
  padding: .2em;
  margin: .2em;
  color: hotpink;
}
```

You can even use pseudo-selectors after this:

```css
example-card::part(heading):hover {
```

However, you _cannot_ select a part within a part:

```css
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:

```html
<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`:

```html
<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.