Skip to content

Component scoped styles incorrectly (?) apply to child components of the same type #15913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Rican7 opened this issue May 14, 2025 · 4 comments

Comments

@Rican7
Copy link
Contributor

Rican7 commented May 14, 2025

Describe the bug

This seems to be a regression of #583.

If a component is nested within another component of the same type, the parent component's scoped styles apply to the child component.

<Foo>
    <!-- This shouldn't receive parent's styles, but it does... -->
    <Foo />

    <!-- While this doesn't. -->
    <Bar />
</Foo>

Notably:

  • This isn't how styling works with other components (they don't receive the parent's scoped styles).
  • This seems to disagree with the documented way that scoping works.
  • This produces behavior that the Svelte compiler tries to prevent with unused selectors (see example below).
  • This creates a sort of redundant behavior that circumvents the intention of the :global selector, and decisions made in Don't cascade styles to nested components? #583.

Reproduction

You can see this in action here:

https://svelte.dev/playground/4e28edac5a874ee2a82dfd9a874d90d1?version=5.28.6

Logs

System Info

System:
    OS: Linux 4.4 Ubuntu 22.04.5 LTS 22.04.5 LTS (Jammy Jellyfish)
    CPU: (12) x64 Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
    Memory: 8.62 GB / 31.74 GB
    Container: Yes
    Shell: 5.1.16 - /usr/bin/bash
  Binaries:
    Node: 22.14.0 - ~/.nvm/versions/node/v22.14.0/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v22.14.0/bin/yarn
    npm: 11.2.0 - ~/.nvm/versions/node/v22.14.0/bin/npm
    pnpm: 9.15.4 - ~/.nvm/versions/node/v22.14.0/bin/pnpm
  npmPackages:
    svelte: ^5.25.11 => 5.28.2

Severity

annoyance

@7nik
Copy link
Contributor

7nik commented May 14, 2025

Styles are scoped against other components. To scope styles down to each component instance, it would need to make unique styles for each component instance, which, in most cases, doesn't make sense and will cause performance issues.

About the unused selector warning: the compiler just checks whether the selector matches any element in the component's markup:
.component.var-alert p - matches;
.component .component - doesn't match (it expects the nested .component to explicitly exist in the markup);
.component :global(.component) - matches (:global(.component) matches with the {@render children?.()}).

So, I really not sure what else can the compiler do here. The only solution is to write more strict selectors by utilizing >, +, etc.

@Ocean-OS
Copy link
Contributor

Ocean-OS commented May 14, 2025

Styles are scoped against other components. To scope styles down to each component instance, it would need to make unique styles for each component instance, which, in most cases, doesn't make sense and will cause performance issues.
So, I really not sure what else can the compiler do here. The only solution is to write more strict selectors by utilizing >, +, etc.

Yeah, I mean the only I can think of this somewhat working would be to have a counter appended to the style hash, and just increment it per each instance. However, this would mean that:

  • component fragments wouldn't be easily cloned, so you'd either have to rebuild the DOM tree manually or do a precalculated traversal of the fragment after cloning it, adding style hashes (which means more nodes having to be accessed)
  • Styles would have to be appended programmatically by the component function

Which would be, as stated before, detrimental for performance and more complex overall.

@Rican7
Copy link
Contributor Author

Rican7 commented May 14, 2025

@7nik

Styles are scoped against other components. To scope styles down to each component instance, it would need to make unique styles for each component instance, which, in most cases, doesn't make sense and will cause performance issues.

Yea, makes sense. I just didn't expect that, wouldn't have known that from the docs, and didn't understand that intuitively from the way that style scoping seemed to work.

About the unused selector warning: the compiler just checks whether the selector matches any element in the component's markup: .component.var-alert p - matches; .component .component - doesn't match (it expects the nested .component to explicitly exist in the markup); .component :global(.component) - matches (:global(.component) matches with the {@render children?.()}).

So, I really not sure what else can the compiler do here. The only solution is to write more strict selectors by utilizing >, +, etc.

Oh yea, no I get that. I was just pointing out the confusing nature of the compiler dissuading you from doing things like that, due to the scope of styles, but then the scoped styles actually affecting other component instances.

@Rican7
Copy link
Contributor Author

Rican7 commented May 14, 2025

@Ocean-OS

Yeah, I mean the only I can think of this somewhat working would be to have a counter appended to the style hash, and just increment it per each instance.

That's almost exactly what I was thinking when I was trying to think of a solution to this myself. I was thinking it could repurpose the $props.id() feature or something, which seemed to have a similar purpose of creating component-instance scoping, just for element identifiers.

However, this would mean that:

  • component fragments wouldn't be easily cloned, so you'd either have to rebuild the DOM tree manually or do a precalculated traversal of the fragment after cloning it, adding style hashes (which means more nodes having to be accessed)
  • Styles would have to be appended programmatically by the component function

Which would be, as stated before, detrimental for performance and more complex overall.

Ah, damn, I didn't know that. I guess I'm not privy to the way that the Svelte compiler and DOM manipulation works, but that does make sense.

Maybe it could be an option on the component somehow, utilizing <svelte:options>?

As is, the behavior is surprising to say the least.

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

No branches or pull requests

3 participants