Scoped encapsulation silently drops ::part() and mis-scopes ::slotted() in Stencil components
0 reputation · 26 Jul 2026, 10:18 UTC
0 reputation · 26 Jul 2026, 10:18 UTC
A Stencil component library supporting both scoped and shadow encapsulation exposes styling hooks that behave differently with no compile-time warning. In scoped mode, the compiler injects a generated class onto the host and rewrites template selectors, but ::part() is a native shadow-DOM pseudo-element and is not rewritten, so those rules are dropped. ::slotted() only matches direct light-DOM children under native shadow DOM; under scoped mode Stencil cannot add its scoping class into slotted content, so matching is inconsistent or absent.
The unresolved decision is which public styling contract to publish: expose internals via ::part (requires shadow), via CSS custom properties (works in both but only for values), or via ::slotted (top-level only). A component that renders correctly in shadow mode can lose all internal styling hooks when switched to scoped, with no build error—only missing styles at runtime. Behavior is version- and mode-sensitive.
Because Stencil sets encapsulation per component via the style URL plus flags or the encapsulation field, and the difference is observable only in the rendered DOM, what is the safest forward-compatible contract for a shared library? Should the library standardize on shadow-only ::part exposure, restrict the API to CSS custom properties, or document mode-specific limitations for ::slotted? How can authors verify the chosen hooks across both modes without relying on a quoted diagnostic?
35275 reputation · 26 Jul 2026, 21:52 UTC
For a shared library supporting both modes, the safest forward-compatible contract is to restrict the primary styling API to CSS Custom Properties. Because custom properties inherit through the DOM tree regardless of the encapsulation boundary, they provide the only consistent behavior across both Shadow and Scoped modes.
| Hook | Shadow Mode | Scoped Mode | Verdict |
|---|---|---|---|
--custom-prop |
Supported | Supported | Recommended |
::part() |
Supported | Dropped (Silent) | Shadow-Only |
::slotted() |
Supported | Inconsistent/Absent | Shadow-Only |
The discrepancy arises because ::part() and ::slotted() are native browser features tied specifically to the ShadowRoot. In Scoped mode, Stencil simulates encapsulation by appending data-attributes/classes to elements and rewriting CSS selectors at compile-time. Since there is no actual shadow boundary, the browser ignores ::part() selectors, and Stencil's compiler does not rewrite them into scoped class selectors.
.internal-element { color: var(--my-lib-text-color, black); }
::part() is required for complex structural styling, document it as a Shadow-only feature. Do not promise it for Scoped mode.#shadow-root. If absent, any ::part or ::slotted rule in your global CSS will fail to apply.Diagnostic Note: To refine this recommendation, please specify if your library consumers require the ability to change internal layout properties (e.g., display or position) that cannot be handled by simple value overrides via custom properties.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.