Limits
Svelte Scoped Props is an experiment. These boundaries are intentional for the alpha.
Not a Svelte core feature
The package is a preprocessor that runs before Svelte parses the component. Native compiler support would have access to Svelte’s already-computed CSS scope hash and would not need to mirror private hash behavior.
Private hash mirror
Svelte does not currently export its default cssHash helper as a public API. This
package mirrors that implementation. If Svelte changes the private default hash,
projects should update this package or pass their own cssHash.
scopedProps({
cssHash: ({ css, filename, hash }) => `svelte-${hash(filename ?? css)}`
})scopedProps({
cssHash: ({ css, filename, hash }) => `svelte-${hash(filename ?? css)}`
})No child type graph
The preprocessor does not inspect the imported child component or its TypeScript prop
types. scoped:class is an explicit usage-site decision by the parent.
That keeps the transform small, but it also means scoped: cannot automatically know
which child props are ClassValue props.
Component tags only
Only uppercase and dotted component tags are supported.
<Child scoped:class="parent-owned" />
<motion.div scoped:class="parent-owned" /><Child scoped:class="parent-owned" />
<motion.div scoped:class="parent-owned" />Native elements are rejected because normal Svelte class behavior already works
there.
Scope before spread
Object spreads cannot express Svelte directive syntax. Use scoped: before props are
collected into an object, then forward the transformed prop normally.
Marker snippet
By default, the transform adds an uncalled snippet marker so Svelte keeps the parent
CSS selectors alive during CSS analysis. The marker is synthesized from the component’s
own selectors: for each scoped selector that has a combinator it emits a chain of <svelte:element this={'x'}> nodes that mirrors the selector’s structure (nested
elements for descendant and child combinators, adjacent siblings for + and ~,
pseudo-classes stripped), plus a flat fallback <svelte:element> that carries every
scoped class at once. Because the marker nodes are dynamically typed, Svelte’s pruner
matches them against any element-qualified selector and they carry no element
content-model or void constraints, so single-compound selectors are defended by the
fallback alone. The marker does not render, but it can leave a small unused function in
compiled output.
scopedProps({ marker: false })scopedProps({ marker: false })Only disable the marker when you are running transform-level tests and are not asking Svelte to compile scoped CSS.
Selector support
The scanner is intentionally small and is not a full CSS parser. For scoped classes it synthesizes marker structure — so these selector forms survive Svelte’s unused-CSS pruning and keep the correct scope hash:
- Compound classes on one element, such as
.page-rows.paged. - Descendant, child, and sibling combinators, such as
.a .b,.a > .b, andspan.a + button.b. - Any element-qualified selector, including void and custom elements, such as
button.foo,input.bar, ormy-widget.baz— the marker element is dynamically typed, so it matches any type and has no void or content-model constraints.
These forms are not matched — only the flat fallback element defends them, so their structural shape can still be pruned:
- Selectors containing attribute selectors, such as
.a[data-open]. - A
:global(...)compound sitting inside a chain. - Functional or positional pseudo-classes such as
:has(),:nth-child(), and:not()— pseudos are stripped, not matched.
Style block is exempt from unused-CSS pruning
A component that uses scoped: effectively opts its <style> block out of Svelte’s
unused-CSS pruning for class-based rules: the marker gives every scoped class rule a
local usage, so Svelte never reports those rules as unused. The tradeoff is that
genuinely dead class CSS in that component will no longer be flagged by svelte-check,
so keep an eye on rules you remove.