Skip to content

Instantly share code, notes, and snippets.

@chkilel
Forked from loilo/pass-slots.md
Created December 28, 2021 22:16
Show Gist options
  • Save chkilel/7947f2134849fc1f7e8864ce882759f7 to your computer and use it in GitHub Desktop.
Save chkilel/7947f2134849fc1f7e8864ce882759f7 to your computer and use it in GitHub Desktop.
Vue: Pass Slots through from Parent to Child Components

Vue: Pass Slots through from Parent to Child Components

The Situation

  • We've got some components A, B and C which provide different slots.
    const A = {
      template: `<div><slot name="a">Default A Content</slot></div>`
    }
    
    const B = {
      template: `<div><slot name="b">Default B Content</slot></div>`
    }
    
    const C = {
      template: `<div><slot name="c">Default C Content</slot></div>`
    }
  • We have a wrapper component W. It receives one of A, B or C as a prop and renders it. W is not aware which slots are provided by either of them.
    Vue.component('W', {
      props: ['child'],
      template: `<component :is="child" />`
    })
  • We want to write a template that uses W but also use the slots offered by the respective wrapped component.

The Problem

Vue won't let us do the following:

<W :child="A">
  <div slot="a">Special A Content</div>
</W>

It just does nothing. Which is very correct, if you think about it: Slot a is expected to be a slot of W, not of the wrapped A.

See this jsFiddle

The Solution

We need to extend our W component's template to pass through any slots it is provided to its wrapped component.

It looks like this:

Vue.component('W', {
  props: ['child'],
  template: `<component :is="child">
  <slot v-for="(_, name) in $slots" :name="name" :slot="name" />
</component>`
})

Now the example will work just fine: jsFiddle

Bonus A: Scoped Slots

Scoped slots like the following...

const D = {
  template: `<div><slot name="d" emoji="🎉">Default D Content</slot></div>`
}

...are not handled by the extended template above. They're just ignored.

However, those can be managed in a very similar fashion to regular slots:

<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData"><slot :name="name" v-bind="slotData" /></template>

Include that alongside the slot passing from above, and our W component will just pass every slot and scoped slot it receives down to its child:

Vue.component('W', {
  props: ['child'],
  template: `<component :is="child">
  <slot v-for="(_, name) in $slots" :name="name" :slot="name" />
  <template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData"><slot :name="name" v-bind="slotData" /></template>
</component>`
})

See it in action in this jsFiddle.

Bonus B: Vue 3

In their essence, scoped slots are just a more powerful superset of "regular" slots. But since introducing them needed a slightly different API, Vue 2 had to distinguish between $slots and $scopedSlots.

Vue 3 took the chance and unified both APIs. The less powerful old slots are gone and the old scoped slots are now just called "slots".

Passing on all slots in Vue 3 is similar (but not exactly equivalent) to passing on scoped slots in Vue 2, so here is the W component from the previous section, adjusted to Vue 3:

Vue.component('W', {
  props: ['child'],
  template: `<component :is="child">
  <template v-for="(_, name) in $slots" v-slot:[name]="slotData"><slot :name="name" v-bind="slotData" /></template>
</component>`
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment