Skip to content

CSS ::part and ::slotted — Deep Dive

DodaTech Updated 2026-06-28 5 min read

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

  1. Using ::slotted to style deeply nested children inside slotted content (::slotted only selects top-level slotted elements).
  2. Forgetting that ::slotted cannot style the slot element itself, only the projected content.
  3. Not using CSS custom properties as a theming fallback alongside ::part.
  4. Using part attribute on elements that are not in a shadow tree (::part only works inside Shadow DOM).
  5. Exposing too many parts, making the component API confusing and creating a maintenance burden.

Practice Questions

  1. What does ::part do? It allows external CSS to style elements inside Shadow DOM that have a part attribute.
  2. What does ::slotted do? It styles elements projected into a slot from Light DOM.
  3. Can ::slotted style grandchildren of slotted content? No. ::slotted only selects top-level slotted elements.
  4. 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

What is the difference between ::part and ::slotted?

::part exposes elements inside Shadow DOM for external styling. ::slotted styles content projected from Light DOM into slots.

Can I use ::part on any element inside Shadow DOM?

Only elements with a part attribute set are exposed via ::part. Elements without part are not accessible.

Does ::slotted work with named slots?

Yes. ::slotted([slot='name']) selects elements projected into a specific named slot.

Can I use ::part with CSS custom properties together?

Yes. This is the recommended pattern. Use custom properties for dynamic values and ::part for structural styling.

What is the exportparts attribute?

exportparts allows a shadow tree to forward parts from an inner custom element to the outer consumer's CSS.

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