Robert Kusznier
Robert Kusznier

Reputation: 6931

How to have attribute inheritance for Vue component slots?

I'm writing a Vue component, which accepts 2 slots:

<template>
  <div class="Component">
    <div class="Component-Label">
      <slot
        name="label"
        class="Component-LabelInner"
      />
    </div>
    <div class="Component-Input">
      <slot
        name="input"
        class="Component-InputInner"
      />
    </div>
  </div>
</template>


<style scoped>
.Component { ... }

.Component-Label { ... }

.Component-LabelInner { ... }

.Component-Input { ... }

.Component-InputInner { width: 100%; ... }
</style>

For layout purposes, I absolutely need to apply some styles to the <slot> elements - the ones with Component-LabelInner and Component-InputInner classes.

(To be precise I need to apply a rule width: 100% to the Component-InputInner, as usually I'll pass there <input> element and I want everything I pass there to stretch to the container.)

The problem is that after <slot> elements get replaced by the content provided to the slots, class attribute isn't inherited (it seems no attributes are inherited on slots) and CSS selectors for .Component-LabelInner and .Component-InputInner don't work.

Can you somehow add CSS classes to the element that <slot> gets replaced with?

Upvotes: 2

Views: 2987

Answers (1)

Max Sinev
Max Sinev

Reputation: 6019

You can not bind class to slot tag. There are some solutions to handle this case:

  1. With Vue mounted hook (it works but looks as bad practice):

Vue.component("slots-comp", {
  template: "#slotsCompTemplate",
  mounted() {
    // each slot is an array, because you can pass a set of nodes wrap them with template tag
    // I use only first VNode for example
    this.$slots.one && this.$slots.one[0].elm.classList.add("one");
    this.$slots.two && this.$slots.two[0].elm.classList.add("two");
  }
});

new Vue({
  el: "#app",
  data: {}
})
.one {
  color: red
}

.two {
  color: blue
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
   <slots-comp>
     <div slot="one">One</div>
     <div slot="two">Two</div>
   </slots-comp>
</div>

<script type="text/x-template" id="slotsCompTemplate">
  <div>
    <slot name="one"></slot>
    <slot name="two"></slot>
  </div>
</script>

  1. Pass neccessary classes as props to scoped slot(it is not fully encapsulated solution):

Vue.component("slots-comp", {
  template: "#slotsCompTemplate",
  data() {
  	return {
      classes: {
      	one: ["one"],
        two: ["two"]
      }
    }
  }
});

new Vue({
  el: "#app",
  data: {}
})
.one {
  color: red
}

.two {
  color: blue
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
   <slots-comp>
     <div slot-scope="props" :class="props.classes.one" slot="one">One</div>
     <div slot-scope="props" :class="props.classes.two" slot="two">Two</div>
   </slots-comp>
</div>

<script type="text/x-template" id="slotsCompTemplate">
  <div>
    <slot :classes="classes" name="one"></slot>
    <slot :classes="classes" name="two"></slot>
  </div>
</script>

  1. Add changes to CSS to apply styles on all internal elements:

    .Component-Input > * 
    {
        /* my rules for child elements */
    }
    
    .Component-Label> * 
    {
        /* my rules for child elements */
    }
    

    Or add wrapper element for slots with classes Component-InputInner etc. and add similar styles for them.

Hope it will help.

Upvotes: 3

Related Questions