fix(framework): create shadow root for components with pre-existing slot content#221
Merged
mohamedmansour merged 3 commits intomainfrom Apr 13, 2026
Merged
Conversation
…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>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
janechu
approved these changes
Apr 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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:
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:
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: