Skip to content

Instantly share code, notes, and snippets.

@loilo
Last active March 27, 2024 20:58
Show Gist options
  • Save loilo/73c55ed04917ecf5d682ec70a2a1b8e2 to your computer and use it in GitHub Desktop.
Save loilo/73c55ed04917ecf5d682ec70a2a1b8e2 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>`
})
@tminich
Copy link

tminich commented Apr 6, 2021

Can someone give a practical example where e.g. in Vuetify you would need this?

Lets say you want to use vee-validate with vuetify, you can write a wrapper component that packs a vuetify component in a ValidationProvider and passes all slots to the wrapped vuetify component without having to explicitly state each and every slot the component provides.

@houfeng0923
Copy link

cool.

@Super-Chama
Copy link

Super-Chama commented Apr 21, 2021

thanks a bunch for this! say what happens to slot props when doing like this? I tried this in Vue3 and slot props seems being ignored.
my bad, this work without an issue 💯

@waelghazie
Copy link

Thanks a lot. I used this to make a wrapper component around vuetify v-data-table and pass scoped slots.

@vhusaruk92
Copy link

Awesome! Thanks! 😊

@willian12345
Copy link

终于找到vue3.0版本的slots转发了,感谢

awesome! ,finally found.
child component inherit all father component's slots and redirect to grand child component in vue3.0 version

@Shadam
Copy link

Shadam commented Aug 12, 2021

Brilliant !

@averri
Copy link

averri commented Aug 16, 2021

Super useful. Thanks for sharing.

@CdTgr
Copy link

CdTgr commented Aug 23, 2021

If anyone is getting an error such as TypeError: Cannot read property 'key' of null in Vue3
I am not sure if this is the perfect solution for it but for now this worked for me

<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
    <slot :name="slot" v-bind="{...scope}" />
</template>

@dangxuanhung
Copy link

Thanks, you save my life

@gnidustotalus
Copy link

gnidustotalus commented Aug 23, 2021

If anyone is getting an error such as TypeError: Cannot read property 'key' of null in Vue3
I am not sure if this is the perfect solution for it but for now this worked for me

<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
    <slot :name="slot" v-bind="{...scope}" />
</template>

This error appears in Vue 3.2 and above.

This is alternative:

<template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
    <slot :name="slot" v-bind="scope || {}" />
</template>

@hunterlord
Copy link

so nice! thank you

@SergkeiM
Copy link

SergkeiM commented Sep 9, 2021

Very useful. Thanks for sharing.

@AntonovDev
Copy link

Thanks!

@ubbcou
Copy link

ubbcou commented Dec 1, 2021

Thanks!

@danielwaltz
Copy link

Yeah this is very helpful! Thanks for the detailed write-up!

@houfeng0923
Copy link

houfeng0923 commented May 20, 2022

Thanks. It work for render, but i found it caused slots re-render unnecessarily. (used vue3 solution in vue 3.2.33). now i use provide/inject
slots instead temporarily)

@robbienohra
Copy link

Fabulous

@Gruski
Copy link

Gruski commented Jul 13, 2022

Using Vue v2.7 and none of the solutions explained above worked. It seems like from v2.6 but before v3 a mix between v2 and v3 worked for me:

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

In any case the post helped a lot. Thanks.

@alxxnder
Copy link

alxxnder commented Nov 30, 2022

(Vue 2.7) In order to get rid of depreciated attributes, and pass "item" variable in the scope I have used this:

<template v-for="(_, name) in $scopedSlots" v-slot:[name]="{item}">
    <slot :name="name" v-bind:item="item" />
</template>

@amirmasoud
Copy link

For some reasons slotProp wasn't working properly with Vue.js 3.x and had following error:

Uncaught (in promise) TypeError: props is null
    renderSlot runtime-core.esm-bundler.js:3009

My solution was the following:

<template v-for="(_, name) in $slots" v-slot:[name]="slotProps">
  <slot v-if="slotProps" :name="name" v-bind="slotProps" />
  <slot v-else :name="name" />
</template>

@Sergdan1992
Copy link

Hi there! Awesome tips! Thank's! What about render function(with wrapper) and pass slots from parant to child? What do u think about it? I have some solution, but also have warning
[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.

function Tag(props, context) {
  const tag = ref(props.tag || 'div')

  const Component = resolveDynamicComponent(tag.value)
  return h(
    Component as DefineComponent,
    context.attrs,
    context.slots
  )
}

@zenzenlin
Copy link

Thanks for sharing. It works.

@biodunbamigboye
Copy link

Thanks

@mutongwu
Copy link

Do you have problems with typscript in vue3 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment