Search code examples
cssvue.jsvue-componentvuejs3vue-slot

Vue 3 slot styles from child component


Summary: I need to style the contents of a <slot>, from the child component. I'm using scoped css and the styles don't apply:

I have the following two components:

<!-- Parent.vue -->
<template>
  <h1>{{ msg }} from Parent</h1>
  <Child>
    <h1>{{ msg }} from Child</h1>
  </Child>
</template>
...
<style scoped>
h1 {
  color: green;
}
</style>

<!-- Child.vue -->
<template>
  <slot></slot>
</template>
...
<style scoped>
h1 {
  color: red;
}
</style>

I want the 2nd <h1> to be red, but it's green, since the component is rendered with something like:

<h1 data-v-452d6c4c data-v-2dcc19c8-s>Hello from Child</h1>

<style>
h1[data-v-452d6c4c] {
  color: green;
}
h1[data-v-2dcc19c8] {
  color: red;
}
</style>

data-v-452d6c4c comes from Parent, and data-v-2dcc19c8-s from Child

If the second attribute, in the <h1> tag, was just data-v-2dcc19c8 the style I wanted would be applied, but since it has that -s suffix (slot?), it doesn't.

I could probably find some other solution with a class or something, but I rarely use <slot> and I want to understand the inner workings. That -s tells me that what I'm trying to do can be dealt with the help of the framework, what am I missing?

A working sample:

https://codesandbox.io/s/condescending-brown-ilfwn


Solution

  • Use the new :slotted selector in Vue 3:

    Child.vue

    <template>
      <slot></slot>
    </template>
    
    <script>
    export default {
      name: "Child",
    };
    </script>
    
    <style scoped>
    :slotted(h1) {
      color: red !important;
    }
    </style>
    

    In Vue 3, child scoped styles don't affect slotted content by default.

    In your particular example, the !important modifier is also necessary because the parent also defined an h1 style which would take precedence otherwise