CSS Parts Inspired by BEM
Tags: webcomponents, javascript, css, webdev
Published: 2022-03-10 16:48:00

In a previous post, [I explored valid names for CSS parts](https://www.abeautifulsite.net/posts/valid-names-for-css-parts/) and discovered that there are very few restrictions in what you can call them. The purpose of that deep dive was to help identify a pattern for naming parts that lets me expose states and _subparts_, or parts exported as a result of composition.

Using inspiration from [BEM](http://getbem.com/), I've settled on a familiar and intuitive pattern that I'd like to share.

## Blocks &rarr; Parts

In BEM terms, a _block_ "encapsulates a standalone entity that is meaningful on its own." Block names consist only of Latin letters, numbers, and dashes. This translates well to CSS parts.

Consider the following custom element template. It's contrived, as its only purpose is to render an image.

```html
<template>
  <!-- shadow root -->
  <img part="image" src="..." alt="...">
</template>
```

If we wanted to make a more descriptive name, we could have called the part `user-provided-image` or something, as long as we stick to letters, numbers, and dashes.

## Elements &rarr; Subparts

In BEM, _elements_ are "parts of a block [that] have no standalone meaning. Any element is semantically tied to its block." An example looks like this.

```html
<div class="block">
  ...
  <span class="block__elem"></span>
</div>
```

Note the two underscores separating the block from the element. You might be wondering how this ties into CSS parts. Since parts are unique to the shadow root, we don't need to namespace them to prevent collisions. Two different custom elements can have two different parts with the same name and that's totally fine.

However, when a custom element is nested inside another custom element, it's often desirable to expose the nested element _and_ its parts, otherwise, consumers won't be able to target it fully with [`::part()`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part).* This means we need to expose the nested element with the [`part`](https://www.w3.org/TR/css-shadow-parts-1/#part-attr) attribute and its parts with the [`exportparts`](https://www.w3.org/TR/css-shadow-parts-1/#exportparts-attr) attribute.

Let's evolve our example so it contains a nested a custom element called `<my-image>`, and let's assume that `<my-image>` has two parts called `photo` and `caption`.

```html
<template>
  <!-- shadow root -->
  <my-image
    part="image"
    exportparts="
      photo:image__photo,
      caption:image__caption
    "
    src="..."
    alt="..."
  >
    ...
  <my-image>
</template>
```

You can see that I've exposed the host element for styling with `part="image"`, which follows the "block" naming convention. Now take a look at the `exportparts` attribute. Conveniently, we can rename subparts when we export them. This lets us avoid collisions (e.g. what if the host element and the nested element have parts of the same name?).

In this example, the host element is exposed through the `image` part, and its `photo` and `caption` subparts are exposed as `image__photo` and `image__caption`, respectively. Notice how everything is scoped to the `image` block now?

End users can now use a very familiar syntax for targeting the nested element and all its parts in their CSS.

```css
::part(image) {
  /* matches the nested <my-image> element */
}

::part(image__photo) {
  /* matches the subpart named photo in <my-image> */
}

::part(image__caption) {
  /* matches the subpart named caption in <my-image> */
}
```

It's not uncommon for custom element authors to neglect to export parts. At the time of this writing, `exportparts` seems to be one of the lesser known features of web components, but [it's well-supported](https://caniuse.com/mdn-html_global_attributes_exportparts) and incredibly powerful.

Anyways, this is feeling pretty good so far!

## Modifiers &rarr; States

Element state is a pretty simple concept. If you have a button, it can have a hover state, a focus state, an active state, etc. Normally, we can target such states with CSS using pseudo selectors.

```css
button:hover {
  /* targets the button's hover state */
}
```

This also works with parts, too.

```css
::part(image):hover {
  /* targets the image part's hover state */
}
```

But not all states are available to target with pseudo selectors, and what if you want to add custom states? More often than not, custom element authors lean on host element attributes for this.

```css
my-image[loaded] {
  /* targets the host element when the image has loaded successfully */
}

my-image[error] {
  /* targets the host element when the image fails to load */
}
```

While this works, mapping stateful parts to attributes on the host element isn't an elegant solution. Let's see how we can improve our example using stateful parts and a BEM-like syntax. In BEM, a _modifier_ is used "to change appearance, behavior or state" and is delimited by two dashes.

Fortunately, parts are designed to work a lot like classes. In fact, they use the same [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList) API as [`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList). This means elements can have more than one part, and part names can be reused throughout the custom element's template!

Evolving our example further, we can add modifier parts to indicate various states. Let's imagine the image in our example has loaded successfully. We can indicate this by adding the `image--loaded` part.

```html
<template>
  <!-- shadow root -->
  <my-image
    part="image image--loaded"
    exportparts="..."
    src="..."
    alt="..."
  >
    ...
  <my-image>
</template>
```

Now we can target the loaded state using `::part()`!

```css
::part(image--loaded) {
  /* targets the image once it has loaded */
}
```

There's no limit to the number of parts an element can have. You can add many additional states if you think they'll be useful.

```html
<template>
  <!-- shadow root -->
  <my-image
    part="
      image
      image--loaded
      image--square
      image--large
      image--jpeg
    "
    exportparts="..."
    src="..."
    alt="..."
  >
    ...
  <my-image>
</template>
```

## Why BEM?

While the examples herein are contrived, I'm hoping you can see the value in using the BEM convention for naming CSS parts. I chose it because it's familiar and it easily represents everything we need: parts, subparts, and states.

Another big win for BEM-inspired part names is that consumers don't have to escape anything in their CSS. It's perfectly valid to name a part `image:loaded`, for example.

```html
<div part="image image:loaded">
```

But your users will need to escape the colon in their stylesheet, otherwise the selector won't match.

```css
::part(image\:loaded) {
  /* this works, but requires a backslash before the colon */
}
```

This may not seem like a big deal but, in the world of CSS, escaping isn't something users typically do and they're probably going to forget. Imagine how frustrating it will be for a user to see a part called `image:loaded` in your documentation and, when they try to implement it, it doesn't work and they don't know why.

Since dashes and underscores don't need to be escaped, they're a more foolproof choice for naming parts.

---

<small>

*The `::part()` selector is [intentionally limited](https://www.w3.org/TR/css-shadow-parts-1/#selectordef-part) by the spec so you can only target elements the custom element author explicitly exposes.