Files
agent-skills/skills/vue-best-practices/reference/rendering-render-function-slots-as-functions.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

169 lines
4.0 KiB
Markdown

---
title: Pass Slots as Functions in Render Functions, Not Direct Children
impact: HIGH
impactDescription: Passing slot content incorrectly causes slots to not render or be treated as props
type: gotcha
tags: [vue3, render-function, slots, children, vnode]
---
# Pass Slots as Functions in Render Functions, Not Direct Children
**Impact: HIGH** - When creating component vnodes with `h()`, children must be passed as slot functions, not as direct children. Passing children directly may cause them to be interpreted as props or fail to render.
## Task Checklist
- [ ] Pass slot content as functions: `{ default: () => [...] }`
- [ ] Use `null` for props when only passing slots to avoid misinterpretation
- [ ] For default slot only, a single function can be passed directly
- [ ] For named slots, use an object with slot function properties
**Incorrect:**
```js
import { h } from 'vue'
import MyComponent from './MyComponent.vue'
// WRONG: Children array may be misinterpreted
h(MyComponent, [
h('span', 'Slot content') // May not render as expected
])
// WRONG: Named slots as direct properties
h(MyComponent, {
header: h('h1', 'Title'), // This is a prop, not a slot!
default: h('p', 'Content') // This is also a prop
})
```
**Correct:**
```js
import { h } from 'vue'
import MyComponent from './MyComponent.vue'
// CORRECT: Default slot as function
h(MyComponent, null, {
default: () => h('span', 'Slot content')
})
// CORRECT: Single default slot shorthand
h(MyComponent, null, () => h('span', 'Slot content'))
// CORRECT: Named slots as functions
h(MyComponent, null, {
header: () => h('h1', 'Title'),
default: () => h('p', 'Content'),
footer: () => [
h('span', 'Footer item 1'),
h('span', 'Footer item 2')
]
})
// CORRECT: With props AND slots
h(MyComponent, { size: 'large' }, {
default: () => 'Button Text'
})
```
## Why Functions?
Slots in Vue 3 are functions for lazy evaluation:
```js
// Slots are called by the child component when needed
// This enables:
// 1. Scoped slots (passing data back)
// 2. Conditional rendering (slot not called if not used)
// 3. Proper reactivity tracking
h(MyList, { items }, {
// Scoped slot - receives data from child
item: ({ item, index }) => h('li', `${index}: ${item.name}`)
})
```
## The null Props Gotcha
When passing only slots, always use `null` for props:
```js
// WRONG: Slots object interpreted as props!
h(MyComponent, {
default: () => 'Hello'
})
// MyComponent receives: props.default = () => 'Hello'
// CORRECT: null signals "no props, next arg is slots"
h(MyComponent, null, {
default: () => 'Hello'
})
// MyComponent receives slot correctly
```
## Forwarding Slots from Parent
```js
export default {
setup(props, { slots }) {
return () => h(ChildComponent, null, {
// Forward all slots from parent
...slots,
// Or forward specific slots
default: slots.default,
header: slots.header
})
}
}
```
## Scoped Slots in Render Functions
```js
// Parent: Receives data from child via slot props
h(DataTable, { data: items }, {
row: (slotProps) => h('tr', [
h('td', slotProps.item.name),
h('td', slotProps.item.value)
])
})
// Child (DataTable): Calls slot with data
export default {
props: ['data'],
setup(props, { slots }) {
return () => h('table', [
h('tbody',
props.data.map(item =>
// Pass data to slot function
slots.row?.({ item })
)
)
])
}
}
```
## Common Patterns
```js
// Wrapper component forwarding slots
h('div', { class: 'wrapper' }, [
h(InnerComponent, null, slots)
])
// Conditional slot rendering
h('div', [
slots.header?.(), // Optional chaining - only render if slot provided
h('main', slots.default?.()),
slots.footer?.()
])
// Slot with fallback content
h('div', [
slots.default?.() ?? h('p', 'Default content when slot not provided')
])
```
## Reference
- [Vue.js Render Functions - Passing Slots](https://vuejs.org/guide/extras/render-function.html#passing-slots)
- [Vue.js Render Functions - Children](https://vuejs.org/guide/extras/render-function.html#children)