Oftentimes you will need to allow your parent Vue components to embed arbitrary content inside of child components. (Angular devs, you know this as transclusion or content projection.) Vue provides an easy way to do this via slots.
Basic Usage
To allow a parent component to pass DOM elements into a child component, provide a <slot></slot>
element inside the child component. You can then populate the elements in that slot with <child-component><p>MyParagraph</p></child-component>
.
ChildComponent.vue
<template> <div> <p>I'm the child component!</p> <!-- Content from the parent gets rendered here. --> <slot></slot> </div> </template>
ParentComponent.vue
<template> <div> <child-component> <p>I'm injected content from the parent!</p> <p>I can still bind to data in the parent's scope, like this! {{myVariable}}</p> </child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data: () => ({ myVariable: `I'm just a lonely old variable.` }) } </script>
Note: If there is no <slot></slot> element in the child's template, any content from the parent will be silently discarded.
Fallback Content
If the parent does not inject any content into the child’s slot, the child will render any elements inside its <slot></slot>
tag, like so:
ChildComponent.vue
<template> <div> <slot> <p>Hello from the child!</p> </slot> </div> </template>
ParentComponent.vue
<template> <div> <!-- Renders "Hello from the parent!" --> <child-component> <p>Hello from the parent!</p> </child-component> <!-- Renders "Hello from the child!" --> <child-component></child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent } } </script>
Named Slots
Having one slot to inject content into is nice and all, but oftentimes it would be preferable to be able to inject various types of content at different locations in the child component. Say you’re making a burger component. You want the top and bottom buns to be predictably at the top and bottom of the burger, (don’t you?) but also want to be able spread things on the bun halves.
Vue allows us to do this by way of named slots. Named slots are simply slots with a name attribute <slot name="slotName"></slot>
to allow for namespaced content injection.
BurgerComponent.vue
<template> <div class="burger-component"> <!-- Elements injected with the `slot="top-bun"` attribute will end up in here. --> <slot name="top-bun"> </slot> <!-- A slot tag without a name is a catch-all, it will contain any content that doesn't have a `slot=""` attribute. --> <slot> </slot> <!-- Elements injected with the `slot="top-bun"` attribute will end up in here. --> <slot name="bottom-bun"> </slot> </div> </template>
SecretRecipeBurger.vue
<template> <!-- TOP SECRET, FOR EMPLOYEE EYES ONLY --> <burger-component> <burger-bun slot="top-bun"> <sesame-seeds></sesame-seeds> <mayonaise></mayonaise> </burger-bun> <burger-bun slot="bottom-bun" :toasted="true"> <secret-sauce></secret-sauce> <!-- I bought it from some hooded guy off the street. --> </burger-bun> <!-- Everything else gets injected into the middle slot (as it's not named.) --> <pickles></pickles> <lettuce></lettuce> <bacon></bacon> <beef-patty></beef-patty> <cheese-slice></cheese-slice> </burger-component> </template> <script> import BurgerComponent from './BurgerComponent.vue'; import { BurgerBun, SesameSeeds, Mayonaise, SecretSauce, Pickles, Lettuce, Bacon, BeefPatty, CheeseSlice } as Ingredients from './ingredients'; export default { components: { BurgerComponent, ...Ingredients }, data: () => ({ price: "priceless" }) } </script>
Obviously I don’t have a clue as to how to make burgers, but that should at least serve as an understandable guide to Vue slots. Enjoy!