CSS ::part and ::slotted — Deep Dive
In this tutorial, you will learn about CSS ::part and ::slotted. We cover key concepts, practical examples, and best practices to help you master this topic.
CSS ::part and ::slotted pseudo-elements allow custom elements to expose styling hooks across shadow boundaries, enabling theming without breaking Encapsulation.
What You'll Learn
- How ::part exposes internal elements for external styling
- How ::slotted styles projected Light DOM content
- The difference between ::part and CSS custom properties
- How to design themable components with ::part
Why It Matters
Component consumers need to style internal elements without breaking encapsulation. CSS ::part provides controlled access. ::slotted styles content projected from Light DOM. Together they solve the theming problem.
flowchart LR A[Component Consumer] --> B[CSS ::part] A --> C[CSS custom properties] A --> D[CSS ::slotted] B --> E[Styled internal element] C --> F[Component uses variable] D --> G[Styled projected content] E -.->|Exported via part attribute| B F -.->|var(--color)| C
The ::part Pseudo-Element
Use the part attribute on elements inside Shadow Dom to expose them for external styling. External CSS uses ::part(name) to target them.
class CardComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.card { border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }
</style>
<div class="card">
<div part="header" class="header">
<slot name="title"></slot>
</div>
<div part="body" class="body">
<slot></slot>
</div>
<div part="footer" class="footer">
<slot name="footer"></slot>
</div>
</div>
`;
}
}
customElements.define('card-component', CardComponent);
/* External CSS can style exported parts */
/* card-component::part(header) { background: #f0f8ff; padding: 16px; } */
/* card-component::part(body) { padding: 16px; } */
/* card-component::part(footer) { background: #f8f8f8; padding: 16px; } */
Multiple Parts on One Element
Elements can have multiple part names, making them targetable through different styling hooks.
class MultiPartElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.container { padding: 16px; border-radius: 8px; }
.container.primary { background: #3498db; color: white; }
.container.secondary { background: #95a5a6; }
</style>
<div part="container surface primary-area" class="container primary">
<slot></slot>
</div>
`;
}
}
customElements.define('multi-part', MultiPartElement);
/* External CSS targeting different part names */
/* multi-part::part(container) { border: 2px solid black; } */
/* multi-part::part(surface) { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } */
/* multi-part::part(primary-area) { font-weight: bold; } */
The ::slotted Pseudo-Element
::slotted selects elements that are projected into a slot from Light DOM. It only selects top-level slotted elements, not their children.
class SlottedStyler extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
::slotted(h2) { color: #e74c3c; font-family: serif; }
::slotted(p) { color: #555; line-height: 1.6; }
::slotted([slot="footer"]) { font-size: 0.8em; color: #999; }
</style>
<article>
<header><slot name="title"></slot></header>
<section><slot></slot></section>
<footer><slot name="footer"></slot></footer>
</article>
`;
}
}
customElements.define('slotted-styler', SlottedStyler);
<!-- Usage -->
<!-- <slotted-styler> -->
<!-- <h2 slot="title">Article Title</h2> -->
<!-- <p>Main content paragraph styled via ::slotted</p> -->
<!-- <p slot="footer">Footer note</p> -->
<!-- </slotted-styler> -->
Combining ::part and CSS Custom Properties
The most powerful pattern exposes CSS custom properties alongside ::part for maximum flexibility.
class ThemedButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
--btn-bg: #3498db;
--btn-text: white;
--btn-radius: 4px;
--btn-padding: 8px 16px;
}
button {
background: var(--btn-bg);
color: var(--btn-text);
border-radius: var(--btn-radius);
padding: var(--btn-padding);
border: none;
cursor: pointer;
font-size: 14px;
}
</style>
<button part="button" id="btn">
<slot></slot>
</button>
`;
}
}
customElements.define('themed-button', ThemedButton);
/* External theming */
/* themed-button { --btn-bg: #e74c3c; --btn-radius: 20px; } */
/* themed-button::part(button) { font-weight: bold; text-transform: uppercase; } */
Exporting Parts from Deeply Nested Components
Parts can be forwarded through nested custom elements using the exportparts attribute.
<outer-component>
<template shadowrootmode="open">
<!-- Forward inner parts to the outer API -->
<inner-component exportparts="inner-header: header, inner-body: body">
</inner-component>
</template>
</outer-component>
<!-- External CSS targets outer-component's parts -->
<!-- outer-component::part(header) { background: blue; } -->
<!-- outer-component::part(body) { padding: 24px; } -->
<!-- These map to InnerComponent's inner-header and inner-body parts -->
Common Mistakes
- Using ::slotted to style deeply nested children inside slotted content (::slotted only selects top-level slotted elements).
- Forgetting that ::slotted cannot style the slot element itself, only the projected content.
- Not using CSS custom properties as a theming fallback alongside ::part.
- Using
partattribute on elements that are not in a shadow tree (::part only works inside Shadow DOM). - Exposing too many parts, making the component API confusing and creating a maintenance burden.
Practice Questions
- What does ::part do? It allows external CSS to style elements inside Shadow DOM that have a part attribute.
- What does ::slotted do? It styles elements projected into a slot from Light DOM.
- Can ::slotted style grandchildren of slotted content? No. ::slotted only selects top-level slotted elements.
- How do you forward parts from nested components? Use the exportparts attribute on the nested element.
Challenge
Build a themable data table component. Use ::part to expose the table header, rows, cells, and footer. Use CSS custom properties for colors and spacing. Create two themes (light and dark) entirely from external CSS without modifying the component.
FAQ
Mini Project
Build a themable modal dialog component. Expose the overlay, dialog surface, title bar, body, and footer as parts. Expose CSS custom properties for colors, spacing, and animation timing. Write a dark theme stylesheet that consumers can import to theme the modal.
What's Next
Lesson 19: Shadow DOM Cross-Framework Usage
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro