Slot content and outlet definition
A slot outlet is a <slot> element in a child component's template that indicates where parent-provided slot content should be rendered. Slot content is the template fragment passed from the parent component. When the component renders, the slot content replaces the <slot> outlet.
Slot content render scope
Slot content has access to the data scope of the parent component because it is defined in the parent. Slot content does not have access to the child component's data. Expressions in Vue templates follow JavaScript's lexical scoping: expressions in the parent template only have access to the parent scope; expressions in the child template only have access to the child scope.
Fallback content in slots
Fallback (default) content can be placed between <slot> tags. This content is rendered only when no content is provided by the parent component. If the parent provides slot content, the provided content overrides the fallback content.
Named slots with v-slot directive
Multiple slot outlets in a single component are defined using the name attribute on <slot> elements. A <slot> without name implicitly has the name "default". To pass content to named slots from the parent, use a <template> element with the v-slot directive, passing the slot name as an argument: <template v-slot:slotName>. The v-slot directive has a shorthand #: <template #slotName>.
Implicit default slot in named slots
When a component accepts both a default slot and named slots, all top-level non-<template> nodes in the parent are implicitly treated as content for the default slot. Only <template> elements are subject to slot targeting; other elements automatically go to the default slot.
Conditional slot rendering with $slots
Use the $slots property in combination with v-if to render content conditionally based on whether a slot has content. $slots is an object where keys are slot names (strings) and values indicate whether that slot has content. This allows wrapping slot content with additional styling only when content is provided.
Dynamic slot names
Dynamic directive arguments work on v-slot, allowing dynamic slot names: <template v-slot:[dynamicSlotName]> or with shorthand <template #[dynamicSlotName]>. The expression is subject to the syntax constraints of dynamic directive arguments.
Scoped slots - passing data from child to parent
Scoped slots allow the child component to pass data to a slot when rendering it. The child passes attributes to a slot outlet just like passing props to a component. The parent template receives slot props using v-slot directive. In the child: <slot text="hello" :count="1" />. In the parent: <ChildComponent v-slot="receivedProps"> {{ receivedProps.text }} {{ receivedProps.count }} </ChildComponent>. The props passed by the child become available as the value of the v-slot directive.
Scoped slots as function analogy
Scoped slots can be understood as functions being passed into a child component. The child component calls the function, passing props as arguments. The parent provides the function body via v-slot, and the child invokes it with data. This is similar to the compiled output and render functions.
Destructuring in v-slot
Like function arguments, v-slot supports destructuring: <ChildComponent v-slot="{ text, count }"> {{ text }} {{ count }} </ChildComponent>. This allows accessing specific props directly without the receiver object.
Named scoped slots
Named scoped slots work similarly to regular named slots, but slot props are accessible via the v-slot directive: <template #header="headerProps"> {{ headerProps }} </template>. The name attribute of a slot is reserved and not included in the props object, so only the passed attributes appear in the received props.
Explicit template required for default scoped slot with named slots
When mixing named slots with default scoped slots, an explicit <template> tag must be used for the default slot. Attempting to place v-slot directly on the component will result in a compilation error. This avoids ambiguity about which scope the default slot props belong to. Use <template #default="{ props }"> explicitly rather than placing v-slot on the parent component.
Scoped slots example - FancyList
A FancyList component can render a list multiple times with different item data while allowing flexible item styling via scoped slots. Parent usage: <FancyList :api-url="url" :per-page="10"> <template #item="{ body, username, likes }"> <div class="item"> <p>{{ body }}</p> <p>by {{ username }} | {{ likes }} likes</p> </div> </template> </FancyList>. Child renders with v-bind to pass object as slot props: <ul> <li v-for="item in items"> <slot name="item" v-bind="item" /> </li> </ul>.
Renderless components pattern
Renderless components encapsulate logic only and do not render anything themselves. Visual output is fully delegated to the consumer component via scoped slots. Example: <MouseTracker v-slot="{ x, y }"> Mouse is at: {{ x }}, {{ y }} </MouseTracker>. Renderless components separate concern of logic from presentation.
Renderless components vs Composition API
While renderless components are a useful pattern for encapsulating logic and composing visual output, most of what can be achieved with renderless components can be achieved more efficiently with Composition API without the overhead of extra component nesting.
Using slots to distribute content to components
The <slot> element acts as a placeholder in a component's template where parent content is inserted. Parent passes content between opening and closing tags: <AlertBox>Something bad happened.</AlertBox>. Child defines where this content appears using <slot /> in its template.
Rendering slots in Composition API
In Composition API render functions, slots are accessed from the setup() context as an object. Each slot is a function that returns an array of vnodes. Access default slot with slots.default() and named slots with slots.footer({ text: props.message }). The slot function receives an object of props to pass to the parent.
Rendering slots in Options API
In Options API render functions, slots are accessed from this.$slots. Each slot is a function that returns an array of vnodes. Access default slot with this.$slots.default() and named slots with this.$slots.footer({ text: this.message }).
Passing slots to child components in render functions
When passing slots to child components, pass either a slot function or an object of slot functions, not an array. Usage: h(MyComponent, () => 'hello') for default slot, or h(MyComponent, null, { default: () => 'default slot', foo: () => h('div', 'foo') }) for named slots. The null argument prevents the slots object from being treated as props. In JSX: <MyComponent>{() => 'hello'}</MyComponent> or <MyComponent>{{ default: () => 'default slot', foo: () => <div>foo</div> }}</MyComponent>.
Scoped slots in render functions
Scoped slots are rendered by passing a slot function with parameters to the child component. The parent passes a slot that receives scoped data: h(MyComp, null, { default: ({ text }) => h('p', text) }). Remember to pass null so slots are not treated as props. The child component invokes the slot: h('div', null, slots.default({ text: text.value })).