๐ŸŒ“ Chapter 83: Shadow DOM

CSS Shadow Parts with ::part()

Exposing controlled styling APIs with `part="..."`, styling sub-elements from outer stylesheets with `::part()`, and forwarding nested parts via `exportparts`.

LEARNING OBJECTIVES โŒต
  • Understand why CSS Shadow Parts (::part()) were introduced to solve the limitations of CSS Custom Property theming.
  • Tag internal Shadow DOM elements using the part="name1 name2" attribute.
  • Style internal shadow elements from outer document stylesheets using custom-element::part(name).
  • Combine ::part() with pseudo-classes (:hover, :active, :focus-visible).
  • Forward shadow parts across nested Web Components using the exportparts attribute.
  • Know the structural limitations of ::part() (why structural descendant selectors like ::part(x) > span are prohibited).
๐ŸŽฌ INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
๐ŸŒ
1. Input
Directives & Tags
โš™๏ธ
2. Parse
Tokenizer & AST
๐ŸŒณ
3. Layout
Box Model & Flow
๐ŸŽจ
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

๐Ÿ“– The Mental Model & Story (Intuitive Foundation)

Imagine purchasing a luxury modular car.

  • Total Lockout (Early Shadow DOM without Parts): The hood is welded shut. You cannot touch or style the internal dashboard, seats, or steering wheel. If you want to change the seat color, the manufacturer must have anticipated that exact desire and exposed a specific custom property (--car-seat-leather-color).
  • Complete Destruction (Piercing CSS / /deep/ - Obsolete & Deprecated): The entire chassis is made of paper. Anyone can rip into the transmission and engine components with a hacksaw, breaking all engineering safety warranties.
  • CSS Shadow Parts (::part()) โ€” The Standard Solution: The car manufacturer installs dedicated customization ports labeled part="steering-wheel", part="driver-seat", and part="dashboard". You can apply any paint, fabric, or texture directly to those specific parts from the outside (my-car::part(driver-seat) { background: leather; }), but you still cannot dismantle the engine block or access internal structural sub-elements.
LIGHT DOM STYLESHEET (Outer Document)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  user-profile::part(avatar) { border-radius: 4px; }         โ”‚
โ”‚  user-profile::part(follow-btn):hover { background: gold; } โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                               โ”‚ Directly styles exposed parts
                               โ–ผ
SHADOW TREE (Inside #shadow-root)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  <img part="avatar" src="avatar.png">  <--- EXPOSED PART    โ”‚
โ”‚  <div class="meta">                                         โ”‚
โ”‚    <span class="user-id">#9482</span>  <--- HIDDEN (Private)โ”‚
โ”‚  </div>                                                     โ”‚
โ”‚  <button part="follow-btn">Follow</button> <--- EXPOSED PARTโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Technical Deep Dive & Specifications

1. The part Attribute & ::part() Pseudo-Element

The W3C CSS Shadow Parts specification allows component developers to designate specific elements within their shadow tree as explicitly styleable by consumers.

<!-- Inside Component's Shadow DOM -->
<button part="btn confirm-action">Save Changes</button>
/* In Outer Document Stylesheet */
my-dialog::part(btn) {
  padding: 10px 20px;
  font-family: inherit;
}

my-dialog::part(confirm-action) {
  background-color: #10b981;
  color: white;
}

2. Supported Pseudo-Classes on Parts

You can append pseudo-classes to ::part() to create dynamic interactive states:

/* Interactive states */
my-dialog::part(confirm-action):hover {
  background-color: #059669;
}

my-dialog::part(confirm-action):focus-visible {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}

my-dialog::part(confirm-action):disabled {
  opacity: 0.5;
}

3. What ::part() CANNOT Do (Architectural Guardrails)

To preserve encapsulation, the specification strictly limits selector traversal past a part:

/* โŒ INVALID: Cannot target descendant children of a part! */
my-dialog::part(btn) > span { color: red; } 

/* โŒ INVALID: Cannot target adjacent siblings of a part! */
my-dialog::part(btn) + p { margin-top: 10px; }

/* โŒ INVALID: Cannot select arbitrary inner pseudo-elements! */
my-dialog::part(input)::placeholder { color: gray; }

4. Forwarding Nested Parts with exportparts

When you build composite Web Components (a component containing other custom elements inside its shadow root), the inner parts are hidden from the outer document by default. To expose them, use the exportparts attribute:

OUTER DOCUMENT
     โ”‚
     โ–ผ ::part(submit-btn)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ <search-form> (Parent Custom Element)                       โ”‚
โ”‚   #shadow-root                                              โ”‚
โ”‚     โ”‚                                                       โ”‚
โ”‚     โ–ผ exportparts="button: submit-btn"                      โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚   โ”‚ <custom-button part="button"> (Inner Custom Element)  โ”‚ โ”‚
โ”‚   โ”‚   #shadow-root                                        โ”‚ โ”‚
โ”‚   โ”‚     <button part="button">Search</button>             โ”‚ โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Syntax Patterns for exportparts:

  • exportparts="inner-part": Exports the inner part with the same name.
  • exportparts="inner-part: outer-name": Renames/maps the inner part to a new public name.
  • exportparts="btn: action-btn, input: search-field": Exports multiple mapped parts separated by commas.

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL example.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

๐Ÿ’ป Interactive Code Playground

Starter Code

Save this file as css-parts.html and open it in your browser:

Line-by-Line Code Breakdown

  • Line 115โ€“119: Inside the shadow tree, elements are decorated with part="card-container", part="product-title", part="price-tag", and part="buy-button".
  • Line 24โ€“47: Outer CSS document targets these parts directly (product-card.theme-emerald::part(buy-button)), granting complete styling freedom over colors, typography, and borders.
  • Line 32, 47: Pseudo-classes like :hover are appended directly to ::part(buy-button):hover to change interaction states.

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

๐Ÿ‹๏ธ Hands-On Exercise

๐ŸŽฏ The Challenge: Build a Nested Component Hierarchy with exportparts

Scenario: You are building an enterprise search bar component <search-bar> which contains an internal <icon-button> custom component. You must export the internal button's part="btn" so external consumer pages can style it using <search-bar>::part(search-btn).

Instructions:

  1. Create a sub-component <icon-button> that has an internal <button part="native-btn">.
  2. Create a parent composite component <search-bar> containing an <input part="input-field"> and <icon-button>.
  3. On <icon-button>, use exportparts="native-btn: search-btn" to forward and rename the part to the outer document.
  4. In the main page CSS, customize <search-bar>::part(input-field) with rounded pill borders and <search-bar>::part(search-btn) with an eye-catching gradient.

๐Ÿ Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

โš ๏ธ Common Pitfalls

  1. Attempting Descendant Selectors: Writing my-element::part(container) p or my-element::part(card) > .title is invalid and will be ignored by the browser parser. Parts expose only the tagged node, not its internal DOM tree.
  2. Over-Exposing Every Single DOM Node: Do not add part="..." to every single div and span. Treat parts as a deliberate design system contract, exposing only semantic UI regions (e.g. header, trigger, panel, close-btn).

๐Ÿ’ก Pro Tips

  1. Multiple Part Names: Elements can have multiple space-separated part names: <div part="badge status-pill warning">. Consumers can target ::part(badge) for general styles and ::part(warning) for specific variants.
  2. Pair Parts with Custom Properties: Use CSS Custom Properties for theme tokens (colors, fonts, radii) and CSS Parts for structural overrides (borders, layout, box-shadows).

๐Ÿ“Œ Key Takeaways

  • The part="name" attribute marks elements inside a shadow root for external styling.
  • The ::part(name) pseudo-element allows outer stylesheets to style tagged shadow elements without breaking encapsulation.
  • Pseudo-classes like :hover, :focus, and :active can be chained onto ::part().
  • Descendant selectors (::part(x) span) are strictly prohibited by the specification.
  • The exportparts attribute forwards nested component parts through parent Web Component boundaries.
  • --
โญ LEARN: HTML ๐ŸŒŸ โš”๏ธ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Which of the following CSS selectors is INVALID according to the CSS Shadow Parts specification?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

How can a custom element expose an internal element with multiple part tokens?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

What does the attribute exportparts="tab: nav-tab, panel: content-panel" accomplish on an inner component?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP