Introduction of web components ::part pseudo-element

The ::part CSS pseudo-element represents any element within a shadow tree.

Let’s say we have below ProfileCard class to define our custom element profile-card.

./components/profile-card.js
export default class ProfileCard extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <h2>Hi there</h2>
      <p>
        My name is ${this.getAttribute('name')}.
        I'm a big fan of web components.
      </p>
      <style>
        /* some styles here inside shadow root */
      </style>
    `
  }
}

customElements.define('profile-card', ProfileCard);
index.html
<profile-card name="Brandon"></profile-card>
index.css
h2 {
  /* this will have no effect on h2 inside our profile-card */
  font-size: 2rem;
}

With current structure, other than setting CSS custom property and pass it down into this component, there is no way to style h2 heading with plain CSS outside of the profile-card’s shadow root. And sometimes it’d be really nice to allow people consuming our components to be able to style the elements inside shadow root. This is where ::part comes in handy.

We’ll need to add a part attribute on h2 heading.

./components/profile-card.js
export default class ProfileCard extends HTMLElement {
  // ...

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <h2 part="heading">Hi there</h2>
      <p>
        My name is ${this.getAttribute('name')}.
        I'm a big fan of web components.
      </p>
      <style>
        /* some styles here inside shadow root */
      </style>
    `
  }
}

Now we can use shiny ::part pseudo-element to style our heading outside of shadow root.

index.css
/* inside () is where the part="green" attribute value */
::part(heading) {
  font-size: 2rem;
}

This is good, but this style applies all custom elements that have a part attribute. We can style the specific element’s part by adding a tag name in front like this.

index.css
profile-card::part(heading) {
  font-size: 2rem; 
}

Like class attribute, part attribute can also accept multiple values. Taking a step further, we’re adding one more value to it.

./components/profile-card.js
export default class ProfileCard extends HTMLElement {
  // ...

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <h2 part="heading uppercase">Hi there</h2>
      <p>
        My name is ${this.getAttribute('name')}.
        I'm a big fan of web components.
      </p>
      <style>
        /* some styles here inside shadow root */
      </style>
    `
  }
}

Now we can change the heading to uppercase.

index.css
profile-card::part(uppercase) {
  text-transform: uppercase;
}

We can also check to see if we have both green and uppercase value.

index.css
/* 
  unlike class name, 
  it needs a space between "green" and "uppercase",
  otherwise it will have on effect
*/
profile-card::part(heading uppercase) {
  color: lightseagreen;
}

One thing to keep in mind is that the ::part() pseudo-element has no CSS specificity, so here color: orange will override color: lightseagreen.

index.css
profile-card::part(heading uppercase) {
  color: lightseagreen;
}

profile-card::part(heading uppercase) {
  color: orange;
}