Skip to content

Star selectors leak scoped styles into children #12906

Open
@thecodewarrior

Description

@thecodewarrior

Vue version

3.5.13

Link to minimal reproduction

https://play.vuejs.org/#eNqNUtFOwjAU/ZWmLyQGNwn6gpNEDSb6oEZ8bGLmeoFi1zZtN2YI/+5t5wAjGt52zz09O/fes6bXxiR1BXREM1dYYTxx4CszZkqURltPbnVpyMzqkvSSNBSB3rtkKkvbB0jFwkNpZO4BK0IyLmpSyNy5K0ZXesVohLERBNLfRZbiC/zK0j0dLJ3/lEBcoQ3wSExQjZyMZsI6f1oshORk3aq9a8vBjsjANMRpKTixwNEmIZvoNSiNaZ96VFMzMU+WTiscOz5ntEAvQoJ9Ml5o5RgddcKM5lLq1UPEvK2g3+HFAoqPA/jSNQFj9NmCA1sDo9uez+0cfNueTB+hwe9ts9S8ksj+p/kCOFwVPLa0m0pxtL3Hi27v4/GEmr+6SeNBuW6oYDQuJfIZxWOGM/w1+s7uMDn/XuYGt9gF4UBujs2FUCr47sIQAnAXztplYYtOAS/Gd/CRYUniD9pRTM45LgPTcWYaDMXPSLzVYMOCcJhhcpEMhnTzBXDwBpE=

Steps to reproduce

  • Create a component Foo
    • Add class="foo" on the root element
    • Add a scoped style .foo * { border: 1px solid red; }
  • Create a component Bar
    • Create multiple nested elements (not just a root element)
  • Add <bar/> into Foo's root

What is expected?

The star selector should be scoped to inside Foo

What is actually happening?

The star selector isn't being scoped, which causes the styles to leak into child elements

System Info

Any additional comments?

The example provided includes a :first-child pseudo-class, which makes the behavior (and use case) a bit clearer, however the scoping issues occur regardless of whether the pseudo-class is specified.

We ran into this issue while attempting to upgrade from Vue 2 to Vue 3, where certain selectors that used to be scoped to immediate children were suddenly applying to deep children. The usage sites themselves were flawed in other ways, and should have been using direct child combinators, but those are workarounds and shouldn't be necessary for scoping to apply correctly.

In the past Vue 3 used to mirror Vue 2's behavior, which scopes only the star selector. (Vue 2 demo). That behavior itself isn't ideal, which was reported in #10548 and then addressed in #10551, however in doing so it caused the styles to leak to children instead of from parents. More manageable, but still not ideal.

Here's a sample of different selectors and how they compile in Vue 2 vs Vue 3 ([--] represents the scoping attribute selector)

Source                Vue 2                     Vue 3
:active               [--]:active               [--]:active
*:active              *[--]:active              [--]:active
.foo :first-child     .foo[--] :first-child     .foo[--] :first-child
.foo *                .foo *[--]                .foo[--] *
.foo *:first-child    .foo *[--]:first-child    .foo[--] *:first-child

The solution for this would be to scope both the final class selector and the star selector:

Source                Vue 3 (fixed)
:active               [--]:active
*:active              [--]:active
.foo :first-child     .foo[--] :first-child
.foo *                .foo[--] [--]
.foo *:first-child    .foo[--] [--]:first-child

Ideally the bare pseudo-class (.foo :first-child) would be fully scoped too, but if the goal is parity with Vue 2, that's not necessary.

Also the ticket that started this, #10548, isn't actually running into a fundamental scoping issue caused by star selectors. The same thing applies to any non-direct-child combinators (demo), but in their case the star selector made it difficult to manage. The only way to solve the fundamental scoping issue would be to add the scope attribute to every level of the selector (.foo .bar :first-child -> .foo[--] .bar[--] [--]:first-child), but I'm guessing that has an adverse performance impact, otherwise it likely would have done that from the start.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🔨 p3-minor-bugPriority 3: this fixes a bug, but is an edge case that only affects very specific usage.scope: sfc

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions