Search code examples
vue.jsvue-componentvuejs3vue3-sfc-loader

Problems with scoped styles and deep styling in vue 3


Im having a lot of problems triying to style a child from parent in vue3.

In this case, i create a generic button with some css properties, and i try to customize this button from other component

Parent

<template>
  <OwnButton
    class="accept-button"
    @ownButtonClicked="emit('accept')"
  >
    <slot> 
      ACCEPT
    </slot>
  </OwnButton>
</template>

<script setup>
import OwnButton from 'path/to/own-button.vue';

const emit = defineEmits(['accept']);
</script>

<style scoped>
.accept-button :deep(.own-button)
{
  background-color  : #4CAF50 !important;
  outline-color     : green !important;
}
.accept-button :deep(.own-button:hover)
{
  background-color: green !important;
}
</style>

Child

<template>
  <button
    class="own-button"
    type="button"
    @click="emit('ownButtonClicked')"
    v-on:keyup.enter="emit('ownButtonClicked')"
  >
    <slot> 
    </slot>
  </button>
</template>

<script setup>

const emit = defineEmits
([
  'ownButtonClicked'
]);
</script>

<style scoped>
.own-button
{
  background-color  : azure;
  outline-color     : lightblue;
  color             : black;
  margin            : 2px;
  padding           : 5px;
  border-radius     : 15px;
  border            : 0;
  box-shadow        : 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
  outline-style     : solid;
  min-width         : 100px;
  max-width         : 150px;
}
.own-button:hover
{
  cursor: pointer;
}
</style>

I tried everything I could think of, including using :deep(button) in parent


Solution

  • This is a design limitation of Vue 3 when dealing with multi-root nodes

    I wrote about this in my article Scoped styles and multi-root nodes don't work well together.

    Understanding the issue

    In Vue 3 we can finally have more than "one root node" components. That is great, but there is a design limitation when doing that. Imagine we have a child component:

    <template>
      <p class="my-p">First p</p>
      <p class="my-p">Second p</p>
    </template>
    

    And a parent component:

    <template>
        <h1>My awesome component</h1>
        <MyChildComponent />
    </template>
    
    <style scoped>
    // There is no way to style the p tags of MyChildComponent
    .my-p { color: red; }
    :deep(.my-p) { color: red; }
    </style>
    

    There is no way from the scoped styling of the multi-root parent component to style the child component's p tags.

    So in short, a multi-root component, can't target multi-root child component's styles with scoped styles.

    Solutions

    👉 💡 The best way to fix that would be to wrap the parent or child component (or both) so we have only one root element.

    But if you absolutely need both to have multi-root nodes, you can:

    1. Use a non-scoped style
    <style>
    .my-p { color: red; }
    </style>
    
    1. Use CSS Modules
    <template>
        <h1>My awesome component</h1>
        <MyChildComponent :class="$style.trick" />
    </template>
    
    <style module>
    .trick {
        color: red;
    }
    </style>
    

    Since we are specifying a class here, then the multi-root child component has to explicitly specify the attribute fallthrough behavior.



    If you want my opinion, unless you absolutely need a multi-root node component, go with a single root node and don't deal with this design limitation at all.