Understanding Shadow DOM and the Need for Parts
Shadow DOM is a web standard that enables encapsulation of HTML, CSS, and JavaScript within a component. When you create a custom element using Shadow DOM, the styles and markup defined inside the shadow tree are isolated from the rest of the document. This isolation prevents style conflicts--for example, a global button class in your main stylesheet won't accidentally affect buttons inside a web component, and component-specific styles won't leak out to affect other elements on the page.
However, this encapsulation creates a challenge: if all styles are isolated, how can developers customize the appearance of internal elements within a component? This is precisely the problem that CSS Shadow Parts was designed to solve. Rather than completely breaking encapsulation, Shadow Parts provides a deliberate, opt-in mechanism for exposing specific elements that component authors want to be customizable.
The Evolution of Component Styling
Before Shadow Parts existed, developers had limited options for styling web component internals. They could use CSS custom properties (variables) that penetrate through the shadow boundary, or they could use JavaScript to manipulate styles at runtime. While CSS custom properties remain a valuable tool for theming and simple customizations, they have limitations in terms of what properties can be controlled and how specific the targeting can be. Shadow Parts complements these approaches by enabling full CSS selector-based styling of explicitly exposed elements, providing significantly more flexibility for component consumers.
How CSS Shadow Parts Work
The CSS Shadow Parts specification defines two key components: the part HTML attribute and the ::part() CSS pseudo-element. Component authors add the part attribute to elements inside their shadow tree that they want to expose for external styling. Consumers of the component can then target these exposed elements using the ::part() pseudo-element in their CSS, selecting them by the name specified in the part attribute.
For teams building modern web applications with custom elements, mastering Shadow Parts is essential for creating flexible, reusable component libraries that balance encapsulation with customization.
1<template id="custom-card">2 <style>3 .card {4 border: 1px solid #e0e0e0;5 border-radius: 8px;6 padding: 16px;7 }8 .header {9 font-size: 1.25rem;10 font-weight: bold;11 margin-bottom: 12px;12 }13 .content {14 color: #424242;15 line-height: 1.5;16 }17 </style>18 <div class="card">19 <div part="card-header" class="header">20 <slot name="title"></slot>21 </div>22 <div part="card-content" class="content">23 <slot></slot>24 </div>25 </div>26</template>The part Attribute: Exposing Elements
The part attribute is a global HTML attribute that can be applied to any element within a shadow tree. When an element has a part attribute, it becomes visible to the outside DOM through the ::part() pseudo-element. The attribute accepts one or more part names, separated by spaces, allowing an element to expose multiple parts if needed.
Multiple Parts on a Single Element
An element can expose multiple parts by including multiple names in the part attribute, separated by spaces. This is useful when an element serves multiple semantic purposes or when you want to enable separate styling contexts for different aspects of the same element:
<div part="tab active" class="tab-item">Tab A</div>
Exporting Parts in Nested Shadow Trees
When shadow trees are nested--meaning a shadow host contains a component that itself uses Shadow DOM--parts are not automatically visible to all ancestors. Each shadow boundary is a separate encapsulation context. To make parts from nested shadow trees visible to outer contexts, the exportparts attribute must be used. This attribute explicitly forwards parts from an inner shadow tree to make them available to outer styling contexts.
Web components built with proper Shadow Parts integration work seamlessly with AI-powered development workflows, enabling intelligent customization and theming capabilities.
CSS Shadow Parts enables powerful styling patterns for web components
Pseudo-Class Support
Combine ::part() with :hover, :focus, and other pseudo-classes for interactive styling
Pseudo-Element Chaining
Style ::before, ::after, and ::first-letter on exposed parts
Multiple Part Names
An element can expose multiple parts for flexible targeting
Nested Component Support
Use exportparts to forward parts through nested shadow boundaries
Practical Example: Custom Card Component
Here's how to create a complete custom element that uses Shadow Parts to expose customizable elements. This example demonstrates best practices for designing component APIs with parts.
JavaScript Component Definition
class CustomCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
--card-border: 1px solid #e0e0e0;
--card-radius: 8px;
}
.card {
border: var(--card-border);
border-radius: var(--card-radius);
padding: 16px;
background: white;
}
</style>
<article class="card">
<header part="card-header card-title" class="header">
<slot name="header"></slot>
</header>
<div part="card-body" class="body">
<slot></slot>
</div>
<footer part="card-footer" class="footer">
<slot name="footer"></slot>
</footer>
</article>
`;
}
}
customElements.define('custom-card', CustomCard);
Consumer Styling with ::part()
/* Style the header */
custom-card::part(card-header) {
font-size: 1.25rem;
font-weight: 600;
color: #333;
}
/* Style the title specifically */
custom-card::part(card-title) {
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Interactive states */
custom-card::part(card-footer):hover {
color: #333;
}
Combining with Pseudo-Classes
One of the powerful features of ::part() is its ability to be combined with pseudo-classes for interactive and state-based styling:
custom-card::part(card-header):hover {
color: #1565c0;
background-color: #f5f5f5;
}
custom-card::part(card-content):focus {
outline: 2px solid #2196f3;
}
Best Practices for Component Authors
Expose Only What You Mean to Expose
When designing a component with Shadow Parts, be intentional about which elements you expose. Parts should be semantically meaningful and stable--they represent a contract between the component author and consumers. Avoid exposing implementation details that might change or that consumers shouldn't need to customize.
Use Descriptive Part Names
Part names should be descriptive and follow consistent naming conventions across your component library. Consider using hyphenated multi-word names for clarity:
card-headeraction-buttontab-content
Provide Documentation
Every component that exposes parts should clearly document which parts are available and what elements they correspond to. Include examples of common customizations and note any styling limitations.
Combine with CSS Custom Properties
While Shadow Parts provide powerful styling capabilities, CSS custom properties (variables) remain valuable for theming and simple customizations. Consider providing both mechanisms:
- Parts for structural and layout customizations
- CSS custom properties for theming values
Performance Considerations
Shadow DOM doesn't inherently impact rendering performance in a negative way. Modern browsers are well-optimized for Shadow DOM rendering, and the encapsulation can actually improve performance by reducing style recalculation scope. The ::part() selector follows the same performance characteristics as other CSS selectors, and modern browser engines optimize selector matching effectively.
For teams implementing modern web applications, combining web components with comprehensive SEO strategies ensures that even highly encapsulated custom elements remain discoverable and optimized for search engines.
Browser Compatibility
CSS Shadow Parts has been widely supported since 2020 and is considered a Baseline feature, meaning it works across all modern browsers including Chrome, Firefox, Safari, and Edge. The specification reached W3C Recommendation status, ensuring stability and interoperability.
| Browser | Version | Status |
|---|---|---|
| Chrome | 89+ | Supported |
| Firefox | 72+ | Supported |
| Safari | 13.1+ | Supported |
| Edge | 89+ | Supported |
| Safari iOS | 13.4+ | Supported |