๐ŸŒ“ Chapter 83: Shadow DOM

The ::slotted() Pseudo-Element

Styling projected Light DOM nodes from inside the shadow tree, the direct-child limitation, and Light DOM vs. Shadow DOM specificity rules.

LEARNING OBJECTIVES โŒต
  • Understand what slotted (projected) nodes are and where they reside in the DOM hierarchy.
  • Target projected Light DOM elements using the ::slotted(<compound-selector>) pseudo-element.
  • Master the Direct Child Limitation: understand why ::slotted() cannot select nested descendant children of slotted nodes.
  • Analyze the specificity and cascade precedence between outer Light DOM styles and internal ::slotted() styles.
  • Style both default and named slots across dynamic component layouts.
๐ŸŽฌ 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 a high-end picture frame displayed in an art gallery.

  • The Frame (Shadow Tree): Built with custom mahogany wood, glass, and internal LED spotlights.
  • The Artwork (Slotted Light DOM Content): Created and owned by an external artist (the parent document). The artist places their painting inside the frame's slot.
  • Styling with ::slotted() (The Frame's LED Spotlight): From inside the frame, the LED spotlights can cast a soft warm glow onto the direct surface of the canvas (::slotted(img) { border-radius: 4px; }).
  • The Limitation (Cannot Touch the Artist's Brushstrokes): The frame's lighting cannot reach inside the canvas to repaint individual paint strokes inside the painting (::slotted(div) p is invalid). Furthermore, if the artist brings their own physical varnish or ink (Light DOM CSS), the artist's styling takes precedence over the frame's lighting.
LIGHT DOM (The Artist's Painting)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  <my-card>                                             โ”‚
โ”‚    <h3 slot="title" class="artist-title">Project X</h3>โ”‚ <--- DIRECT SLOTTED NODE
โ”‚    <div slot="body">                                   โ”‚ <--- DIRECT SLOTTED NODE
โ”‚      <p>Nested child paragraph</p>                     โ”‚ <--- NESTED DESCENDANT (Untouchable by ::slotted)
โ”‚    </div>                                              โ”‚
โ”‚  </my-card>                                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚ Projected into Slot
                            โ–ผ
SHADOW TREE (The Frame)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  #shadow-root                                          โ”‚
โ”‚    <slot name="title"></slot>                          โ”‚
โ”‚    <slot name="body"></slot>                           โ”‚
โ”‚                                                        โ”‚
โ”‚  ::slotted(h3)      ===> MATCHES direct <h3 slot="title"> โ”‚
โ”‚  ::slotted(div)     ===> MATCHES direct <div slot="body"> โ”‚
โ”‚  ::slotted(p)       ===> โŒ FAILS! (p is inside div)   โ”‚
โ”‚  ::slotted(div p)   ===> โŒ INVALID CSS SYNTAX!         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Technical Deep Dive & Specifications

1. What is Content Projection?

When a custom element contains children in the Light DOM, the browser does not move them in the DOM tree. Instead, it projects them through <slot> elements into the visual Flat Tree.

<!-- Light DOM (Host Document) -->
<article-card>
  <h2 slot="headline">Breaking Technology News</h2>
</article-card>
<!-- Inside Component's Shadow DOM -->
<div class="card-wrapper">
  <slot name="headline"></slot>
</div>

2. The ::slotted() Selector Syntax

Inside the shadow root stylesheet, you use ::slotted() to apply default styles to projected elements:

/* Styles any top-level slotted node */
::slotted(*) {
  box-sizing: border-box;
}

/* Styles top-level slotted <h2> elements */
::slotted(h2) {
  margin: 0 0 10px 0;
  font-family: var(--font-heading, sans-serif);
  color: #1e293b;
}

/* Styles top-level slotted elements having class .lead */
::slotted(.lead) {
  font-size: 1.15rem;
  color: #64748b;
}

3. The Direct-Child Restriction (Crucial Spec Rule)

According to the CSS Scoping Module Level 1 specification:

The ::slotted() pseudo-element matches only the first-level child nodes distributed to a slot. It never matches descendants of those nodes.

<!-- Light DOM -->
<user-card>
  <div class="user-info">
    <span class="user-name">Sarah Connor</span>
  </div>
</user-card>
Selector in Shadow Root Matches? Reason
::slotted(.user-info) โœ… YES .user-info is the direct slotted node
::slotted(div) โœ… YES Direct slotted tag matches div
::slotted(.user-name) โŒ NO .user-name is a descendant inside div
::slotted(div) .user-name โŒ NO Combinators after ::slotted() targeting descendants are invalid
::slotted(div > span) โŒ NO Argument inside ::slotted() must be a single compound selector

4. Light DOM vs. Shadow DOM Specificity on Slotted Content

Slotted elements live in the Light DOM, which means they are subject to both Light DOM stylesheets and Shadow DOM ::slotted() styles.

Specificity Order for Slotted Elements (Lowest to Highest):
1. Shadow DOM default fallback content inside <slot>Fallback</slot>
2. Shadow DOM `::slotted(...)` styles
3. Outer Light DOM styles targeting the element (e.g. `article-card h2`)
4. Outer Light DOM styles with `!important`

[!IMPORTANT] Light DOM Always Wins: If an outer stylesheet has h2 { color: blue; } and the shadow root has ::slotted(h2) { color: red; }, the rendered text will be blue! Light DOM rules naturally override shadow ::slotted() rules unless ::slotted() uses !important.


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 slotted-demo.html and open it in your browser:

Line-by-Line Code Breakdown

  • Line 66โ€“71: ::slotted(img[slot="media"]) enforces image geometry (width: 100%, height: 180px, object-fit: cover) on the slotted image tag.
  • Line 74โ€“78: ::slotted(h2[slot="title"]) provides baseline title typography.
  • Line 14โ€“17: The outer document defines .custom-title { color: #38bdf8; }. Because outer Light DOM rules take precedence over shadow ::slotted() rules, the title text renders sky blue.
  • Line 81โ€“86: ::slotted(p) formats all paragraphs projected into the default unnamed slot.

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 Reusable Tab Panel with Styled Slotted Tabs

Scenario: You are building an accessible <tab-group> custom element. Consumers project their tab buttons and content panels into slots. You must use ::slotted() to format the projected elements into clean, modern UI components.

Instructions:

  1. Create a custom element <tab-group> with an open shadow root.
  2. In the shadow template, provide <slot name="tabs"></slot> inside a tab bar container, and <slot name="panels"></slot> inside a content area.
  3. Use ::slotted(button[slot="tabs"]) to style tab triggers (background: transparent, border: none, color: #94a3b8, padding: 10px 16px, cursor: pointer, font-size: 14px, border-bottom: 2px solid transparent).
  4. Use ::slotted(button[slot="tabs"][aria-selected="true"]) to highlight the active tab (color: #38bdf8, border-bottom-color: #38bdf8, font-weight: 600).
  5. Use ::slotted(section[slot="panels"]) to style the tab panels (padding: 16px, color: #e2e8f0).

๐Ÿ 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. Trying to Style Slotted Descendants: ::slotted(div) span or ::slotted(div span) does not work. ::slotted() can only select direct children assigned to the slot.
  2. Forgetting that Light DOM CSS beats ::slotted(): If an element inside a slot has an outer CSS rule, the outer rule will override your ::slotted() rule. Use ::slotted() for sensible defaults, not mandatory overrides.

๐Ÿ’ก Pro Tips

  1. Compound Selector Inside Parentheses: You can pass complex compound selectors inside ::slotted(), e.g., ::slotted(a.btn[target="_blank"]:hover).
  2. Combine with Slot Name Attribute: Use ::slotted([slot="header"]) to explicitly target content intended for specific slots.

๐Ÿ“Œ Key Takeaways

  • Slotted nodes remain in the Light DOM; they are only visually projected into the shadow tree.
  • The ::slotted(selector) pseudo-element styles direct children assigned to a <slot>.
  • ::slotted() cannot target nested descendants of slotted elements.
  • Light DOM stylesheets take precedence over shadow ::slotted() rules.
  • ::slotted(*) is useful for setting universal base margins and box-sizing on all projected nodes.
  • --
โญ LEARN: HTML ๐ŸŒŸ โš”๏ธ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Given the markup <my-card><div class="wrapper"><p>Hello</p></div></my-card>, which selector inside the shadow root of <my-card> will successfully style the paragraph?

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

What happens if an outer document stylesheet declares h1 { color: green; } and a custom element's shadow root declares ::slotted(h1) { color: red; }?

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

Which argument to ::slotted() is syntactically INVALID?

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