Skip to content

bug: Stencil ignores children for slotting #6296

@DanielMathis

Description

@DanielMathis

Prerequisites

Stencil Version

4.35.0

Current Behavior

We have used StencilJS extensively to build up a component library for our company. Now, as we are integrating them into our web-site, we consistently running in some issues, namely that sometimes (randomly!) Stencil fails to slot children correctly initially.

We are using scoped web-components (without shadow-dom) and all slot-fixes applied, as well as the "dist-custom-elements" target. This issue seems to appear with named and un-named slots with nearly all components, whether they are a complex or simple containers.

Here you can see, how the right-most component does not render correctly as the others.
Image

Here the malformed HTML:

<option-picker name="chip-1" legend="Speichergröße" align="start" class="hydrated"><!---->
  <fieldset id="fca9de69-f131-49ae-b5b4-491eaa8ec050" class="option-picker layout-spacing-medium align-start">
    <legend class="option-picker__legend">Speichergröße</legend>
    <div class="option-picker__label" aria-hidden="true">Speichergröße<span
        class="option-picker__label__value-text"></span></div>
    <div class="option-picker__options"></div>
  </fieldset>
  <option-picker-item value="option-1" variant="chip" class="hydrated"><!---->
    <div class="option-picker-item variant-chip"><label class="option-picker-item__label"
        for="907e8ca6-bc95-4e40-b566-4d10d217b515"></label><input id="907e8ca6-bc95-4e40-b566-4d10d217b515"
        class="option-picker-item__input" name="chip-1" type="radio" value="option-1"><span class="focus-indicator"
        aria-hidden="true"></span></div>128 GB
  </option-picker-item>
  <option-picker-item value="option-2" variant="chip" class="hydrated"><!---->
    <div class="option-picker-item variant-chip"><label class="option-picker-item__label"
        for="cb6c1352-a984-49a4-a844-9b5d4cb117e4">256 GB</label><input id="cb6c1352-a984-49a4-a844-9b5d4cb117e4"
        class="option-picker-item__input" name="chip-1" type="radio" value="option-2"><span class="focus-indicator"
        aria-hidden="true"></span></div>
  </option-picker-item>
  <option-picker-item value="option-3" variant="chip" class="hydrated"><!---->
    <div class="option-picker-item variant-chip"><label class="option-picker-item__label"
        for="df1b4100-a9af-4ba5-9144-819705125614">512 GB</label><input id="df1b4100-a9af-4ba5-9144-819705125614"
        class="option-picker-item__input" name="chip-1" type="radio" value="option-3"><span class="focus-indicator"
        aria-hidden="true"></span></div>
  </option-picker-item>
</option-picker>

Each option-picker-item element is supposed to be slotted into fieldset div "option-picker__options".

A re-render appears to solve the problem, such as when the user interacts with the component and causes a re-render. As such, our current workaround is to set a timeout (~20ms) in the "componentDidLoad" lifecycle method, which then sets a flag (annotated with @State) to trigger a re-render.

However, this is not an ideal solution, and small enough timeouts appear to also fail in some cases. We suspect there might be some race-condition in the shadow-dom polyfill or rendering logic.

Expected Behavior

Stencils slots all children correctly.

The expected HTML in our case would be

<option-picker name="chip-2" class="hydrated"><!----><span slot="legend" hidden="">Speichergröße</span>
  <fieldset id="c19a62ca-6a39-43a2-99e1-9596d91ac11d" class="option-picker layout-spacing-medium align-center">
    <div class="option-picker__options">
      <option-picker-item value="option-1" variant="chip" class="hydrated"><!---->
        <div class="option-picker-item variant-chip"><label class="option-picker-item__label"
            for="2ec7153b-28e4-4d81-906a-78a1447dcfe5">128 GB</label><input id="2ec7153b-28e4-4d81-906a-78a1447dcfe5"
            class="option-picker-item__input" name="chip-2" type="radio" value="option-1"><span class="focus-indicator"
            aria-hidden="true"></span></div>
      </option-picker-item>
      <option-picker-item value="option-2" variant="chip" class="hydrated"><!---->
        <div class="option-picker-item variant-chip"><label class="option-picker-item__label"
            for="00dd93d9-529f-46e5-a932-67b8f04eb17d">256 GB</label><input id="00dd93d9-529f-46e5-a932-67b8f04eb17d"
            class="option-picker-item__input" name="chip-2" type="radio" value="option-2"><span class="focus-indicator"
            aria-hidden="true"></span></div>
      </option-picker-item>
      <option-picker-item value="option-3" variant="chip" class="hydrated"><!---->
        <div class="option-picker-item variant-chip"><label class="option-picker-item__label"
            for="672c55bc-07d5-4fa2-bab9-9966689740b5">512 GB</label><input id="672c55bc-07d5-4fa2-bab9-9966689740b5"
            class="option-picker-item__input" name="chip-2" type="radio" value="option-3"><span class="focus-indicator"
            aria-hidden="true"></span></div>
      </option-picker-item>
    </div>
  </fieldset>
</option-picker>

System Info

System: node 20.10.0
Platform: darwin (24.0.0)
CPU Model: Apple M2 (8 cpus)
Compiler: <private>/node_modules/.pnpm/@[email protected]/node_modules/@stencil/core/compiler/stencil.js
Build: 1749776174
Stencil: 4.35.0 🌝
TypeScript: 5.5.4
Rollup: 4.34.9
Parse5: 7.2.1
jQuery: 4.0.0-pre
Terser: 5.37.0

The problem could not be reproduced on MacOS, but rather on Windows machines in the Chrome, Edge, and FireFox browsers.

Steps to Reproduce

Create a web-component with slots (nested within some divs).

Then render this component multiple times with children (to increase the chance of the error to occur). Sometimes, this bug will occur, and page-refreshes might be necessary to finally see the error.

We use this config:

export const config: Config = {
  namespace: 'gucci-common-web-components',
  outputTargets: [
    {
      type: 'dist-custom-elements',
      customElementsExportBehavior: 'bundle',
      externalRuntime: false
    },
    {
      type: 'dist-hydrate-script',
      dir: '.dist-hydrate'
    },
    {
      type: 'docs-json',
      file: 'dist/docs/component-docs.json',
    }
  ],
  extras: {
    experimentalSlotFixes: true,
    experimentalScopedSlotChanges: true
  }
};

Code Reproduction URL

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting ReplyThis PR or Issue needs a reply from the original reporter.Bug: ValidatedThis PR or Issue is verified to be a bug within StencilHelp Wanted

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions