Kendall Spackman
Kendall Spackman

Reputation: 21

How to use classes to style child component's root and non-root elements in Vue 3?

I'm starting to implement a reusable Vue component library for in-house use on a team of 20+ developers across a couple of applications. A common pattern I have used in the past in Vue 2 is applying a class to a child component to be able to to style it as needed from the parent. This was an intentional feature by the Vue team, according to this documentation. In Vue 2, even when inheritAttrs was false, the class (and style) attributes still fell through to the root element, perhaps for this reason. Something like this:

Parent:

<template>
  <div>
    <MyInput class="my-child" />
  </div>
</template>

<style scoped>
/* This style gets applied to the root <div> */
.my-child {
  width: 300px;
}
</style>

MyInput:

<template>
  <!-- Even with inheritAttrs: false, classes are bound here in Vue 2 -->
  <div>
    <label>...</label>
    <input v-bind="$attrs" ... />
  </div>
</template>

<script>
export default {
  inheritAttrs: false
}
</script>

In Vue 3, this was a breaking change introduced - the class now gets applied to the child that the attributes are bound to using v-bind="$attrs" (if applicable), whether using inheritAttrs: false or in the case of a multi-root component. But that means that (I believe) there is no simple way to style the root element. Nor the child element that is inheriting attributes with scoped styles, for that matter, because of how scoping works.

Parent:

<template>
  <div>
    <MyInput class="my-child" />
  </div>
</template>

<style scoped>
/* This style does nothing in Vue 3 - to the root element nor the child element */
.my-child {
  width: 300px;
}
</style>

MyInput:

<template>
  <div>
    <label>...</label>
    <!-- With inheritAttrs: false, classes are bound here in Vue 3, but can't be targeted by scoped styles -->
    <input v-bind="$attrs" ... />
  </div>
</template>

<script>
export default {
  inheritAttrs: false
}
</script>

In this situation, without creating global style rules,

  1. is there any way to apply styles to the MyInput root div from the parent? AFAIK, there isn't, except maybe something like this:

    Parent:

    <style scoped>
    div {
    ...
    }
    </style>
    

    Not very useful. Or to add something like a containerStyles prop that can be applied to the root div:

    <template>
      <div :style="containerStyles">
        <label>...</label>
        <input v-bind="$attrs" ... />
      </div>
    </template>
    
    <script>
    export default {
      inheritAttrs: false,
      props: {
        containerStyles: {
          type: Object,
          default: null
        }
      }
    }
    </script>
    
  2. what are my options for applying styles to the child input? Here are the solutions I'm aware of:

    1. Use the style attribute.
      <template>
        <MyInput style="width:300px" />
      </template>
      
    2. Use the :deep() selector.
      <template>
        <MyInput class="my-child" />
      <template>
      
      <style scoped>
      :deep(.my-child) {
        width: 300px;
      }
      </style>
      
    3. Use module styling.
      <template>
        <MyInput class="$style.myChild" />
      </template>
      
      <style module>
      .myChild {
        max-width: 300px;
      }
      </style>
      
  3. Is there a best practice for something like this? Especially if with multiple components, say for example, MyButton has the classes and styles bound to a root button element, MyInput has them bound to a child input element, and MyDialog is a multi-root component with them bound to a dialog element. How do you make these use cases consistent?

Upvotes: 2

Views: 37

Answers (0)

Related Questions