Skip to content

fix(framework): create shadow root for components with pre-existing slot content#221

Merged
mohamedmansour merged 3 commits intomainfrom
fix/shadow-dom-slot-hydration
Apr 13, 2026
Merged

fix(framework): create shadow root for components with pre-existing slot content#221
mohamedmansour merged 3 commits intomainfrom
fix/shadow-dom-slot-hydration

Conversation

@mohamedmansour
Copy link
Copy Markdown
Contributor

@mohamedmansour mohamedmansour commented Apr 11, 2026

When a shadow-DOM component (meta.sd=1) has child nodes already present at connectedCallback time, must still create a shadow root. Previously, the condition:

    else if (this.childNodes.length > 0) { root = this; isSSR = true; }

treated ALL elements with children as SSR light-DOM content, skipping shadow root creation entirely. This is wrong for shadow-DOM components whose children are slot content — not SSR output.

This bug surfaces during SPA partial rendering: the server sends back a parent template containing child custom elements with their projected content already inlined:

    <o-button size=\"m\"><svg>…</svg><span>Reply</span></o-button>

The browser parses this into an with childNodes. When the element upgrades, sees children and assumes SSR hydration, leaving the element without a shadow root. The button renders unstyled and its template (with ) is never applied.

The fix checks meta.sd (explicit shadow-DOM declaration in the compiled template) before falling into the SSR light-DOM path:

    else if (this.childNodes.length > 0 && !meta.sd) { … }

When meta.sd is set, the element proceeds to the wantShadow branch which calls attachShadow() and populates from the template. Existing children remain in the light DOM and project through as intended.

When meta.sd is NOT set (e.g. components using global __webui_shadow fallback), children are still treated as SSR content — preserving the existing light-DOM hydration path for the shadow-via-global-flag case.

Includes a Playwright regression test (slot-shadow fixture) covering:

  1. Empty child — baseline, always worked
  2. Child with pre-existing slot content (static HTML)
  3. Dynamically spawned child with slot content (programmatic)

…lot content

When a shadow-DOM component (meta.sd=1) has child nodes already present
at connectedCallback time, \() must still create a shadow root.
Previously, the condition:

    else if (this.childNodes.length > 0) { root = this; isSSR = true; }

treated ALL elements with children as SSR light-DOM content, skipping
shadow root creation entirely. This is wrong for shadow-DOM components
whose children are slot content — not SSR output.

This bug surfaces during SPA partial rendering: the server sends back a
parent template containing child custom elements with their projected
content already inlined:

    <o-button size=\"m\"><svg>…</svg><span>Reply</span></o-button>

The browser parses this into an <o-button> with childNodes. When the
element upgrades, \ sees children and assumes SSR hydration,
leaving the element without a shadow root. The button renders unstyled
and its template (with <slot>) is never applied.

The fix checks meta.sd (explicit shadow-DOM declaration in the compiled
template) before falling into the SSR light-DOM path:

    else if (this.childNodes.length > 0 && !meta.sd) { … }

When meta.sd is set, the element proceeds to the wantShadow branch
which calls attachShadow() and populates from the template. Existing
children remain in the light DOM and project through <slot> as intended.

When meta.sd is NOT set (e.g. components using global __webui_shadow
fallback), children are still treated as SSR content — preserving the
existing light-DOM hydration path for the shadow-via-global-flag case.

Includes a Playwright regression test (slot-shadow fixture) covering:
  1. Empty child — baseline, always worked
  2. Child with pre-existing slot content (static HTML)
  3. Dynamically spawned child with slot content (programmatic)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread packages/webui-framework/tests/fixtures/slot-shadow/element.ts Fixed
mohamedmansour and others added 2 commits April 11, 2026 00:50
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
@mohamedmansour mohamedmansour requested review from a team, akroshg and toddreifsteck April 11, 2026 14:25
@mohamedmansour mohamedmansour merged commit 341a9a1 into main Apr 13, 2026
21 checks passed
@mohamedmansour mohamedmansour deleted the fix/shadow-dom-slot-hydration branch April 13, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants