CSS ::part() Pseudo-Element

The ::part() pseudo-element allows CSS to style specific elements inside a web component's Shadow DOM when those elements are exposed using the part attribute. It provides a controlled way to customize web components without directly accessing their internal HTML structure.

Syntax

selector::part(part-name) {
  property: value;
}

The selector identifies the shadow host or web component.

The part-name identifies an element exposed through its part attribute.

For example:

custom-card::part(header) {
  background-color: #0066cc;
  color: #fff;
}

This rule styles an exposed part named header inside a <custom-card> web component.

The name inside ::part() must match a part name exposed by the component.

Part names are case-sensitive CSS identifiers and are not enclosed in quotation marks.

The ::part() pseudo-element does not create a new element or part. It selects an existing element that the component exposes for styling.

How It Works

Web components can use Shadow DOM to keep their internal structure and styles separate from the surrounding webpage.

Ordinary CSS selectors generally cannot select internal elements across a shadow boundary.

The ::part() pseudo-element provides a controlled exception for elements that the component explicitly exposes.

The process involves four steps:

  1. Create a web component with a shadow root.
  2. Add a part attribute to an internal element.
  3. Identify the web component using a CSS selector.
  4. Use ::part() to style the exposed element.

For example, a component might expose an internal button:

<button part="action">Submit</button>

The surrounding webpage can style that button with:

custom-form::part(action) {
  background-color: #0066cc;
  color: #fff;
}

The component controls which internal elements are exposed.

The webpage controls the styles applied to those exposed parts.

This approach allows customization without requiring access to the component's complete internal structure.

Understanding Shadow DOM

Shadow DOM is a browser feature that allows an element to contain an encapsulated DOM tree.

The element containing the shadow root is called the shadow host.

For example:

<custom-card></custom-card>

JavaScript can attach a shadow root to the custom element:

const card = document.querySelector("custom-card");
const shadow = card.attachShadow({ mode: "open" });
shadow.innerHTML = "<div>Card content</div>";

The internal <div> belongs to the shadow tree rather than the surrounding document tree.

An ordinary document-level selector such as the following does not select that internal element:

custom-card div {
  color: red;
}

Shadow DOM encapsulation prevents ordinary selectors from crossing the shadow boundary.

However, the component can expose selected internal elements using the part attribute.

Those exposed elements can then be styled with ::part().

Note: Shadow DOM can use either an open or closed shadow root. Exposed parts can be styled through ::part() in either case.

The part Attribute

The HTML part attribute identifies an element as an exposed styling part of a shadow tree.

For example:

<div part="header">Card Header</div>

When this element belongs to a component's shadow tree, the surrounding webpage can style it using:

custom-card::part(header) {
  background-color: #0066cc;
  color: #fff;
}

The part attribute accepts a space-separated list of part names.

For example:

<div part="header featured">Featured Header</div>

The same element can be targeted using either part name:

custom-card::part(header) {
  padding: 15px;
}
custom-card::part(featured) {
  background-color: #0066cc;
}

Both rules apply to the same exposed element.

The part attribute does not automatically make an element a web component. It identifies styling parts within a shadow tree.

Basic Example

The following example creates a custom card component with an exposed header.

HTML:

<demo-card-basic></demo-card-basic>

CSS:

demo-card-basic::part(header) {
  background-color: #0066cc;
  color: #fff;
  padding: 12px;
  font-weight: bold;
}

JavaScript:

customElements.define("demo-card-basic", class extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    shadow.innerHTML = `
      <div part="header">CSS Web Component</div>
      <p>This card contains an exposed header.</p>
    `;
  }
});
Browser Result

The header receives a blue background and white text from the document-level CSS.

The paragraph inside the shadow tree remains unchanged.

Only the element exposed with part="header" is targeted by the rule.

Styling Multiple Parts

A web component can expose several internal elements using different part names.

For example:

<div part="container">
  <h3 part="title">CSS Reference</h3>
  <p part="description">Learn CSS properties and selectors.</p>
</div>

The surrounding webpage can style each part independently:

custom-card::part(container) {
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 15px;
}
custom-card::part(title) {
  color: #0066cc;
  font-size: 20px;
}
custom-card::part(description) {
  color: #555;
  line-height: 1.6;
}

The container part controls the outer appearance.

The title part controls the heading.

The description part controls the paragraph.

Each part must be explicitly exposed by the component.

This technique allows a component to provide a stable styling interface without exposing every internal element.

Multiple Part Names

An internal element can expose more than one part name.

For example:

<button part="button primary">Save Changes</button>

The button can be styled through either name:

custom-form::part(button) {
  padding: 10px 16px;
  border: 0;
  border-radius: 4px;
}
custom-form::part(primary) {
  background-color: #0066cc;
  color: #fff;
}

The first rule applies general button styling.

The second rule applies the primary button colors.

Both declarations affect the same internal button.

Part names are separated by spaces in the HTML attribute.

To select an element exposing both names, use the corresponding ::part() syntax:

custom-form::part(button primary) {
  font-weight: bold;
}

This selector matches an exposed element that has both button and primary as part names.

Combining ::part() with Pseudo-Classes

The ::part() pseudo-element can be followed by supported pseudo-classes to style different states of an exposed element.

For example:

custom-form::part(button) {
  background-color: #0066cc;
  color: #fff;
}
custom-form::part(button):hover {
  background-color: #004c99;
}
custom-form::part(button):focus-visible {
  outline: 3px solid #ffcc00;
  outline-offset: 2px;
}

The first rule defines the button's normal appearance.

The :hover rule changes the background when a pointing device hovers over the button.

The :focus-visible rule provides a visible keyboard focus indicator.

State-based styling is useful for buttons, links, and other interactive elements exposed by web components.

Not every pseudo-class or pseudo-element combination is permitted after ::part().

For example, selectors cannot freely traverse the internal shadow tree after selecting a part.

The exportparts Attribute

The exportparts attribute allows a web component to forward exposed parts from a nested shadow host.

This is useful when one web component contains another web component.

For example, an inner component exposes a part named label:

<span part="label">CSS Reference</span>

An outer component contains that inner component inside its shadow tree:

<inner-card exportparts="label"></inner-card>

The exportparts attribute forwards the inner component's label part through the outer component.

The surrounding webpage can then style it using:

outer-card::part(label) {
  color: #0066cc;
  font-weight: bold;
}

Part names can also be renamed while forwarding them:

<inner-card exportparts="label: card-label"></inner-card>

The forwarded part is now exposed as card-label.

outer-card::part(card-label) {
  color: #0066cc;
}

Without appropriate forwarding, a part exposed by a nested component is not automatically available through every surrounding shadow host.

The exportparts attribute provides a controlled way to expose nested styling parts.

Example

The following example creates a reusable web component with exposed title, description, and button parts.

HTML:

<demo-card-styled></demo-card-styled>

CSS:

demo-card-styled::part(container) {
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 15px;
  background-color: #f8f9fa;
}
demo-card-styled::part(title) {
  color: #0066cc;
  font-size: 20px;
  font-weight: bold;
}
demo-card-styled::part(description) {
  color: #555;
}
demo-card-styled::part(button) {
  background-color: #0066cc;
  color: #fff;
  border: 0;
  border-radius: 4px;
  padding: 10px 16px;
  cursor: pointer;
}
demo-card-styled::part(button):hover {
  background-color: #004c99;
}
demo-card-styled::part(button):focus-visible {
  outline: 3px solid #ffcc00;
  outline-offset: 2px;
}

JavaScript:

customElements.define("demo-card-styled", class extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    shadow.innerHTML = `
      <div part="container">
        <h3 part="title">CSS Reference</h3>
        <p part="description">Explore CSS properties and selectors.</p>
        <button part="button" type="button">Learn More</button>
      </div>
    `;
  }
});
Browser Result

The component displays a styled card with a blue heading and button.

The surrounding webpage controls the exposed parts through ::part().

Hovering over the button changes its background color.

Keyboard focus displays a visible outline.

The component's internal structure remains inside its shadow tree.

::part() vs. ::slotted()

The ::part() and ::slotted() pseudo-elements both relate to Shadow DOM, but they target different kinds of content.

::part()

The ::part() pseudo-element allows styles outside a shadow tree to target internal elements explicitly exposed as parts.

custom-card::part(title) {
  color: #0066cc;
}

This rule styles an internal element exposed with the part name title.

::slotted()

The ::slotted() pseudo-element is used inside a shadow tree's stylesheet to style elements assigned to slots.

::slotted(p) {
  color: #0066cc;
}

This rule styles slotted paragraph elements from within the component's shadow stylesheet.

The main distinction is that ::part() exposes internal elements for external styling, while ::slotted() styles assigned light DOM elements from within a shadow tree.

Both help web components provide flexible styling while preserving Shadow DOM boundaries.

Supported CSS Properties

Unlike some other pseudo-elements, ::part() is not restricted to a small set of text-related CSS properties.

It can apply ordinary CSS styling to exposed elements, subject to the element's normal CSS behavior and the cascade.

Common property groups include:

Property Group Examples
Color color, background-color
Font font-family, font-size, font-weight
Spacing margin, padding
Border border, border-radius
Layout display, width, height
Visual Effects box-shadow, opacity, transform
Transitions transition

For example:

custom-card::part(container) {
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

These declarations style the exposed container using ordinary CSS box-model and visual properties.

The component's internal styles and the external part styles participate in the CSS cascade according to Shadow DOM styling rules.

Limitations

The ::part() pseudo-element has several important limitations.

  • It can style only elements exposed through part names.
  • It cannot select arbitrary internal elements across a shadow boundary.
  • Part names must match those exposed by the component.
  • Nested component parts may require exportparts to be accessible externally.
  • It does not provide direct DOM access to internal elements.
  • It does not allow arbitrary descendant selectors to traverse a shadow tree.
  • Its styling can be affected by the component's internal CSS and the cascade.

For example, the following selector does not provide a valid way to select an arbitrary descendant inside an exposed part:

custom-card::part(container) h3 {
  color: red;
}

The ::part() pseudo-element does not expose the entire internal subtree for ordinary descendant selection.

To allow external styling of the heading, the component should expose that heading as its own part.

<h3 part="title">CSS Reference</h3>

The surrounding webpage can then use:

custom-card::part(title) {
  color: red;
}

Accessibility Considerations

Styling exposed parts should preserve the usability and accessibility of the web component.

Follow these accessibility practices:

  • Maintain sufficient contrast between text and background colors.
  • Preserve visible keyboard focus indicators on interactive parts.
  • Avoid removing outlines without providing an accessible replacement.
  • Ensure buttons and controls remain large enough to use comfortably.
  • Test component styling at different screen sizes and zoom levels.
  • Do not use visual styling as a substitute for semantic HTML or accessible names.
  • Ensure custom styling does not hide essential content or controls.

The ::part() pseudo-element changes presentation but does not automatically provide accessibility semantics.

For example, an exposed button should still be implemented using an appropriate interactive element with a meaningful accessible name.

Custom styles should preserve the component's intended behavior for keyboard and assistive technology users.

Browser Support

The ::part() pseudo-element is widely available in modern browsers, including Chrome, Edge, Firefox, and Safari.

Older browser versions may not support the pseudo-element or related Shadow DOM features.

When ::part() is unsupported, the external styling rule is ignored and the component continues to use its internal or default styles.

For current compatibility information, visit Can I Use? .

Specifications

The ::part() pseudo-element is defined in the CSS Shadow Parts Module Level 1 .

The specification describes how shadow parts are exposed, how part names are matched, and how the exportparts attribute forwards parts through nested shadow trees.

Shadow DOM behavior is also defined by the WHATWG DOM Standard .