htmlvue.jsvuejs2vue-componentvue-slot

Vue.js how to use attributes on slots


is it possible to set attributes on a slot and the element from the parent gets these attributes?

Parent

<vDropdown>
  <button slot="button">new button</button>
  <ul>content</ul>
</vDropdown>

Dropdown.vue

<div>
  <slot name="button" aria-haspopup="true">
    //fallback
    <button aria-haspopup="true">Default Button</button>
  </slot>
  <div id="name" :aria-expanded="expanded">
    <slot />
  </div>
</div>

the output for the button is without any attributes...

<div>
  <button>new button</button>
  <div id="myDropdown" aria-expanded="false">
    <ul>content</ul>
  </div>
</div>

Solution

  • Use Scoped Slots

    Step 1. In the parent, update the old deprecated slot targeting syntax slot="button" to the v-slot directive:

    Parent.vue

    ...
    <template v-slot:button>                   ✅
      <button>new button</button>
    </template>
    ...
    
    <button slot="button">new button</button>  ❌
    

    How to target a slot in Vue 2.6.0+

    Step 2. Next, understand that any attribute bindings you add to a <slot> tag will become available to any slotted content placed there (these are called "slot props"):

    Dropdown.vue

    <slot name="button" :aria-haspopup="true">
    

    Step 3. Vue automatically creates an object containing every binding from Step 2, and passes that to the v-slot expression, i.e. slotProps below. You can then use the special v-bind="" syntax to spread all those bindings onto the button:

    Parent.vue updated

    <template v-slot:button="slotProps">
      <button v-bind="slotProps">new button</button>
    </template>
    

    Here's a demo, but sadly it requires a hack using two hyphens when you do this with a kebab-case attribute. I'll plan to submit an issue for this in the Vue GitHub repo.

    Vue.component('dropdown', {
      template: `
      <div>
        <slot name="button" aria--haspopup="true">
          <button aria-haspopup="true">Default Button</button>
        </slot>
        <div id="name" :aria-expanded="expanded">
          <slot />
        </div>
      </div>`,
      data() {
        return {
          expanded: true
        }
      }
    });
    
    new Vue({
      el: "#app",
    });
    .aria-haspopup {
      background: orange;
    }
    <div id="app">
      <dropdown>
        <template v-slot:button="slotProps">
          <button v-bind="slotProps">new button</button>
        </template>
        <ul>content</ul>
      </dropdown>
    </div>
    
    <script src="https://unpkg.com/vue"></script>