Scoped style does not work with multi-root child components
Version
3.2.31
Reproduction link
Steps to reproduce
See preview here: sfc.vuejs.org/
What is expected?
First 3 divs should have blue border
What is actually happening?
Only first div has blue border
It's by design, in my view.
The scoped data property is only inherited onto single root nodes because the intended use case is to add layout styles for that root node.
That doesn't make too much sense for a multi-root component.l, and could in fact have unintended side effects when the number of root nodes changes during the lifecycle of the child component.
So for multi-root components, the right approach would be to add a wrapper element in the parent for layouting purposes.
Provided that @yyx990803 confirms this I'd say we should make the docs more explicit about this.
That doesn't make too much sense for a multi-root component
The sense is that multiple root elements may use classes:
And I may need to apply styles to these classes.
But I can't achieve this neither with :deep pseudo class nor without it:
Adding or not adding v-bind="$attrs" also does not help:
Here is an updated example: sfc.vuejs.org I have very simple need. I need to add blue border to each section in a component that needs to be multi-root. How can I do this? I think this is very simple task that I should be able to acomplish with flexible framework.
The sense is that multiple root elements may use classes:
Our perspective is that it's not a good idea to break separation of concerns/responsibilities in this way, so we don't go out of our way to support it.
But I can't achieve this neither with :deep pseudo class nor without it:
Yes, :deep doesn't work either in your specific example because it would need one element at least in the parent wrapping the children:
working with a root element in App.vue
How can I do this?
in this specific scenario where the parent has no elements of its own around Child
, and Child
has multiple roots:
a) Use a non-scoped style
b) use css modules instead of scoped
:
<template>
<Comp class="$styles.section" style="background: yellow;" />
<Comp2 class="$styles.section" style="background: yellow;" />
</template>
<style module>
.section {
border: 2px solid blue;
}
</style>
@LinusBorg from the comments is a bit unclear if you answered or understood this issue with multi-root components so I am going to give it a try to ask again because in my view this is a bug.
Component with multi-root nodes cannot be styled from parent with :deep()
selector
If we have a HelloWorld.vue
component with this code:
<template>
<div class="hello-world">Hi</div>
<div class="hello-world-2" v-bind="$attrs">Hey</div>
</template>
Then from the parent component, we can't style it within scoped
style with :deep()
selector nor with by accessing a class we add in the parent:
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld class="hello" />
</template>
<style scoped>
.hello {
background: blue;
color: white;
}
:deep(.hello) {
background: blue;
color: white;
}
:deep(.hello-world-2) {
background: blue;
color: white;
}
</style>
So as you can see above, none of the 3 ways works to style the elements of the child component. This fails only when mutli-root nodes are used in the child component.
Trying to understand your answers
Maybe there is something that I am missing here but how can this behavior be by design? Since multi-root components are supported, why do we have to wrap child components in order to enable :deep()
styling?
There's a difference between a bug and a limitation of a design/solution/feature, wether that limitation is for technical reasons or intentionally put in place.
:deep()
:deep(.some-class-name)
was explicitly designed to produce a selector like: [data-v-3478937439] .some-class-name
.
That means, this design requires that the child component that you want to have the deep styles bleed into needs to be wrapped in one element in the parent which can carry the data-v-3478937439
attribute. That's how this feature works. Thats the design.
The fact that you can't use it in the specific way you want it to does not make it a bug. It makes it a feature that has a limitation falling short of your desired behavior. If you can come up with a feature proposal of how we can make scoped deep styles work without the requirement of such a wrapper element, and without it being a breaking change - feel free to open an RFC.
Also, I gave you a working solution which uses module
instead of scoped
. CSS modules work differently and thus can make it happen, but have different trade-offs.
multiple root nodes.
Back in Vue 2, root nodes were required, and the fact that scoped styles could bleed onto the root node was a double-edged sword:
- It allowed for an escape hedge when one needs to layout a component without a wrapper element.
- But it also meant that style encapsulation of scoped styles was leaky, and not 100% reliable. You can have class name collisions with this on root elements. People were and are bitten by this quite regularly.
During development and RFC phases of Vue 3, we considered removing this behavior completely because of 2., but left it in for single-root-components for two reasons:
- Allow for easier migration: We don't force people migrating to re-organize for wrapper-elements in multiple places.
- Point 1. above does have it's merits, especially when it's about layouting a child in relation to the parent - which also doesn't really affect multi-root components as layouting usually is done on wrapper elements.
Now, was this decision perfect? Certainly not, it has trade-offs. Does this mean it's a bug? Absolutely not. But does this mean that there are edge cases where this design is giving users a hard time? absolutely.
Can we change it without a major release? No.
@LinusBorg thank you for the detailed explanation. Make sense to me now why this is a limitation. Have a nice weekend
I believe a parent component (P) should be able to consume a child component (C) without knowing if C is a single or multi-root component.
If, in component P, we set some CSS class for C, the styles tied to that class should fall through C no matter the root plurality. In my opinion, That API is consolidated in the VUE design and should not be broken for multi-root components.
That fallthrough behavior is a decision for C to control. If it is a simple single root element, that is already settled and working, in the other case of C being a multi-root component, the v-bind="$attrs"
would guide the entire fallthrough behavior, including classes and styles coming from the parent.
That makes de API predictable. Someone read this doc and goes one applying v-bind=$attrs
as a way to signal which of the root nodes is the nominal target for fallthroughs.
It is not my intention to trash the framework design. I love VUE and use it daily. I also acknowledge that it is common for technical challenges to limit capabilities. That said, if this behavior is permanent, I think it should be declared as a limitation in here and/or here