mcpField
S
shadow-dom-overlay-insertion

shadow-dom-overlay-insertion

github.com/editframeDevelopment1971 installsUpdated 2w ago
Repository

About shadow-dom-overlay-insertion

shadow-dom-overlay-insertion is a technical reference for inserting overlays from web components that may be slotted inside a shadow DOM. It describes how to choose the insertion container and handle the timing gap before Lit has rendered its shadow DOM.

Key features

  1. 01Detect slotted components with `assignedSlot`
  2. 02Insert overlays in the shadow-internal container
  3. 03Handle pre-render slot assignment timing
  4. 04Choose insertion behavior for each DOM state

Use cases

  1. 01Add overlays to slotted web components
  2. 02Prevent overlays from landing in the host light DOM
  3. 03Retry insertion after Lit renders

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/editframe/skills --skill shadow-dom-overlay-insertion

After installing, type/shadow-dom-overlay-insertionin Claude Code to confirm it loaded.

Details

From the project README

Shadow DOM Overlay Insertion

The Problem

When component A (e.g. EFCanvas) tries to insert an overlay into the DOM next to itself, it typically uses this.parentElement.appendChild(overlay). This breaks when A is slotted inside a shadow DOM host (e.g. EFWorkbench).

EFWorkbench (shadow host)
  shadow root
    <div class="canvas-slot-container">   ← the real DOM parent
      <slot name="canvas">                ← slot element
        [EFCanvas is assigned here]       ← light DOM, assigned via slot
  • efCanvas.parentElement returns the shadow host (EFWorkbench), not the internal container
  • Calling shadowHost.appendChild(overlay) places the overlay in the host's light DOM, where CSS and z-index from the shadow DOM won't apply — the overlay is invisible

The Fix Pattern

private insertOverlay(overlay: HTMLElement): void {
  const slot = this.assignedSlot;

  if (slot) {
    // Correctly slotted: insert into the shadow-internal container
    slot.parentElement?.appendChild(overlay);
    return;
  }

  if (this.parentElement?.shadowRoot) {
    // Shadow DOM exists but slot assignment hasn't happened yet
    // (connectedCallback fires before Lit renders shadow DOM).
    // Return early; caller should retry via requestAnimationFrame.
    return;
  }

  // Not slotted — plain DOM, safe to use parentElement
  this.parentElement?.appendChild(overlay);
}

The Timing Hazard

During HTML parsing, connectedCallback fires before Lit has rendered the shadow DOM. At that point:

  • this.assignedSlot is null (slot assignment hasn't happened)
  • this.parentElement is non-null (the shadow host)
  • this.parentElement.shadowRoot is also null (shadow DOM not yet attached)

Later in the same microtask tick (after Lit's performUpdate):

  • this.assignedSlot becomes the <slot> element
  • slot.parentElement is the internal container

Guard pattern:

connectedCallback() {
  super.connectedCallback();
  this.tryInsertOverlay();
}

private tryInsertOverlay(): void {
  if (!this.assignedSlot && this.parentElement?.shadowRoot) {
    // Too early — shadow host exists but slot not yet assigned.
    // Lit will assign the slot after performUpdate.
    requestAnimationFrame(() => this.tryInsertOverlay());
    return;
  }
  this.insertOverlay(this.overlay);
}

Summary of Rules

SituationassignedSlotparentElement.shadowRootAction
Not slottednullnull/undefinedUse parentElement.appendChild
Correctly slottednon-null(any)Use assignedSlot.parentElement.appendChild
Too early (Lit not rendered)nullnon-nullReturn early, retry via RAF
Slotted, parent has no shadownullnullUse parentElement.appendChild

Never use this.parentElement directly when the parent is a shadow DOM host.

Comments

Markdown supported