shadow-dom-overlay-insertion
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.parentElementreturns 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.assignedSlotisnull(slot assignment hasn't happened)this.parentElementis non-null (the shadow host)this.parentElement.shadowRootis also null (shadow DOM not yet attached)
Later in the same microtask tick (after Lit's performUpdate):
this.assignedSlotbecomes the<slot>elementslot.parentElementis 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
| Situation | assignedSlot |
parentElement.shadowRoot |
Action |
|---|---|---|---|
| Not slotted | null | null/undefined | Use parentElement.appendChild |
| Correctly slotted | non-null | (any) | Use assignedSlot.parentElement.appendChild |
| Too early (Lit not rendered) | null | non-null | Return early, retry via RAF |
| Slotted, parent has no shadow | null | null | Use parentElement.appendChild |
Never use this.parentElement directly when the parent is a shadow DOM host.
More from editframe/skills
video-analysis
Analyze video files using ffprobe, mp4dump, and jq. Use when investigating video samples, keyframes, MP4 box structure, codec info, packet timing, or debugging video playback issues.
75visual-thinking
Create visual analogies by mapping relational structure from familiar domains onto unfamiliar concepts using spatial relationships to make abstract patterns concrete. Covers static diagrams AND animated video storytelling (camera choreography, race comparisons, pacing). Use when explaining complex concepts, creating analogies, designing diagrams, creating explainer animations, or revealing system structure.
71css-animations
CSS animation fill-mode requirements for Editframe timeline system. Use when creating CSS animations, debugging flashing/flickering issues, or when user mentions animation problems, fade effects, slide effects, or sequential animations.
69threejs-compositions
Integrate Three.js 3D scenes into Editframe compositions via addFrameTask. Scenes are pure functions of time, fully scrubable, and renderable to MP4. Use when creating 3D animations, WebGL content in compositions, or integrating Three.js with Editframe's timeline system.
66editor-gui
Build video editing interfaces using Editframe's GUI web components. Assemble timeline, scrubber, filmstrip, preview, and playback controls like lego bricks. Use when creating video editors, editing tools, or when user mentions timeline, scrubber, preview, playback controls, trim handles, or wants to build editing UIs.
64elements-new-package
Create a new @editframe/* workspace package in the elements monorepo and publish it to npm.
64