Skip to content

The part Attribute

Global 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.

<!-- 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>
ValueDescription
Part name (string)Name to expose for styling (space-separated for multiple parts)
Result
Result
Result
Result

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>
`;
}
}

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;
}

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>
// Good: Semantic, reusable part names
shadow.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 unclear
shadow.innerHTML = `
<div part="div1">
<div part="div2">
<span part="span1"></span>
</div>
</div>
`;
/**
* <my-button>
* Exposed parts:
* - button: The main button element
* - icon: Button icon (if present)
* - label: Button text label
* </my-button>
*/
class MyButton extends HTMLElement { }
// Good: Consistent across components
part="header"
part="body"
part="footer"
// Bad: Inconsistent
part="top"
part="content"
part="bottom-section"
// Good: Expose key styling points
shadow.innerHTML = `
<div part="container">
<button part="button">
<slot></slot>
</button>
</div>
`;
// Bad: Every element exposed
shadow.innerHTML = `
<div part="div">
<span part="span1">
<span part="span2">
<button part="button">
<span part="span3"></span>
</button>
</span>
</span>
</div>
`;
/* ✗ Doesn't work */
my-component::part(button)::before {
content: "»";
}
/* ✓ Workaround: Expose another part */
my-component::part(button-icon) {
/* Style the icon part */
}
/* ✗ Doesn't work */
my-component button::part(label) { }
/* ✓ Works */
my-component::part(label) { }
/* ✗ Doesn't work (parts inside parts) */
my-component::part(container)::part(button) { }
/* ✓ Workaround: Use exportparts on intermediate component */
const element = shadowRoot.querySelector('[part]');
console.log(element.part); // DOMTokenList
console.log(element.part.contains('button')); // true/false
// Modify parts
element.part.add('active');
element.part.remove('disabled');
element.part.toggle('selected');
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;
}
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';
}
}
BrowserSupport
Chrome73+
Firefox72+
Safari13.1+
Edge79+