The part Attribute
The part Attribute
Section titled “The part Attribute”The part attribute exposes elements inside a Web Component’s Shadow DOM for styling from outside the component. It enables theme-ability and customization while maintaining encapsulation, allowing component users to style specific parts without breaking the component’s internal structure.
Syntax
Section titled “Syntax”<!-- Inside shadow DOM --><button part="button">Click me</button><span part="label icon">Text with icon</span>
<!-- Outside, in light DOM CSS --><style> my-component::part(button) { background: blue; }</style>Values
Section titled “Values”| Value | Description |
|---|---|
| Part name (string) | Name to expose for styling (space-separated for multiple parts) |
Examples
Section titled “Examples”Basic Themeable Button
Section titled “Basic Themeable Button” Result
Styled Card Component
Section titled “Styled Card Component” Result
Custom Input with Exposed Parts
Section titled “Custom Input with Exposed Parts” Result
Multiple Parts on Same Element
Section titled “Multiple Parts on Same Element” Result
How It Works
Section titled “How It Works”Exposing Parts
Section titled “Exposing Parts”Inside shadow DOM, use part attribute:
class MyComponent extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = ` <div part="container"> <button part="button">Click</button> <span part="label">Label</span> </div> `; }}Styling Parts
Section titled “Styling Parts”Outside, use ::part() pseudo-element:
my-component::part(container) { background: white; border: 2px solid #e2e8f0; border-radius: 8px;}
my-component::part(button) { background: #3b82f6; color: white; padding: 10px 20px;}
my-component::part(label) { color: #64748b;}Multiple Part Names
Section titled “Multiple Part Names”Elements can have multiple part names:
<!-- Inside shadow DOM --><button part="button primary">Primary Button</button>
<!-- Style either part --><style> my-component::part(button) { /* Styles */ } my-component::part(primary) { /* Styles */ }</style>Best Practices
Section titled “Best Practices”1. Expose Logical Parts
Section titled “1. Expose Logical Parts”// Good: Semantic, reusable part namesshadow.innerHTML = ` <div part="container"> <header part="header"> <h2 part="title"></h2> </header> <main part="body"></main> <footer part="footer"></footer> </div>`;
// Bad: Too granular or unclearshadow.innerHTML = ` <div part="div1"> <div part="div2"> <span part="span1"></span> </div> </div>`;2. Document Exposed Parts
Section titled “2. Document Exposed Parts”/** * <my-button> * Exposed parts: * - button: The main button element * - icon: Button icon (if present) * - label: Button text label * </my-button> */class MyButton extends HTMLElement { }3. Use Consistent Naming
Section titled “3. Use Consistent Naming”// Good: Consistent across componentspart="header"part="body"part="footer"
// Bad: Inconsistentpart="top"part="content"part="bottom-section"4. Don’t Over-Expose
Section titled “4. Don’t Over-Expose”// Good: Expose key styling pointsshadow.innerHTML = ` <div part="container"> <button part="button"> <slot></slot> </button> </div>`;
// Bad: Every element exposedshadow.innerHTML = ` <div part="div"> <span part="span1"> <span part="span2"> <button part="button"> <span part="span3"></span> </button> </span> </span> </div>`;Limitations
Section titled “Limitations”Cannot Style Pseudo-Elements
Section titled “Cannot Style Pseudo-Elements”/* ✗ Doesn't work */my-component::part(button)::before { content: "»";}
/* ✓ Workaround: Expose another part */my-component::part(button-icon) { /* Style the icon part */}Cannot Combine with Element Selectors
Section titled “Cannot Combine with Element Selectors”/* ✗ Doesn't work */my-component button::part(label) { }
/* ✓ Works */my-component::part(label) { }Cannot Access Nested Parts Directly
Section titled “Cannot Access Nested Parts Directly”/* ✗ Doesn't work (parts inside parts) */my-component::part(container)::part(button) { }
/* ✓ Workaround: Use exportparts on intermediate component */JavaScript API
Section titled “JavaScript API”Checking Part Attribute
Section titled “Checking Part Attribute”const element = shadowRoot.querySelector('[part]');console.log(element.part); // DOMTokenListconsole.log(element.part.contains('button')); // true/false
// Modify partselement.part.add('active');element.part.remove('disabled');element.part.toggle('selected');Common Patterns
Section titled “Common Patterns”Themeable Component
Section titled “Themeable Component”class ThemeCard extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = ` <div part="card"> <header part="header"> <slot name="title"></slot> </header> <div part="body"> <slot></slot> </div> <footer part="footer"> <slot name="actions"></slot> </footer> </div> `; }}/* Light theme */theme-card::part(card) { background: white; color: black;}
/* Dark theme */.dark theme-card::part(card) { background: #1e293b; color: white;}State-Based Parts
Section titled “State-Based Parts”class ToggleButton extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' }); this.isOn = false;
shadow.innerHTML = ` <button part="button"> <span part="label">Toggle</span> </button> `;
shadow.querySelector('button').addEventListener('click', () => { this.isOn = !this.isOn; this.updateParts(); }); }
updateParts() { const button = this.shadowRoot.querySelector('button'); button.part = this.isOn ? 'button active' : 'button'; }}Browser Support
Section titled “Browser Support”| Browser | Support |
|---|---|
| Chrome | 73+ |
| Firefox | 72+ |
| Safari | 13.1+ |
| Edge | 79+ |
Related Concepts
Section titled “Related Concepts”exportpartsattribute - Forward parts from nested components::part()CSS pseudo-element - Select exposed parts- Shadow DOM - Encapsulated DOM trees
slotattribute - Content projection