🇬🇧 English

Scoped Slot Props Undefined in Vue 3: Why It Happens and the Fix

Vue 3 returns undefined scoped slot props when a parent omits the slot, because the child renders fallback content and never allocates a props object. Guarding

Key Takeaways
  • In a template: v-if="$slots.mySlot". $slots is available on every component instance without imports. A slot that the parent never filled is simply absent from the object, so the check evaluates falsy and the branch is skipped. This is the fastest fix for a blank panel or an empty table header.
  • For the default slot by name: v-if="$slots.default". Anonymous children passed between a component's tags land under the key default, not under the component's own name. Teams write $slots.content and then wonder why their wrapper renders nothing when the call site was <Card>Some text</Card>.
  • In setup: const slots = useSlots(). Imported from vue, it returns the same object the template sees. The catch is timing: during setup() itself the parent's render has not run, so the object can be empty. Read slots.default inside a computed, a watch, or the render function, never in the bare body of setup.
  • Presence is not callability. if (slots.mySlot) tells you a function exists. It does not tell you the function returns anything useful, and it does not tell you the props you plan to hand it are populated. Guard the call site, then guard the data.
  • $scopedSlots is gone. Vue 2.6, released February 2019, introduced v-slot and kept $scopedSlots as a compatibility layer; Vue 3 removed it entirely, folding scoped and normal slots into one $slots object. Any snippet mixing $scopedSlots.header with a Vue 3 project is 2019 vintage and will be undefined at runtime.
  • For TypeScript, defineSlots beats runtime checks. Added in Vue 3.3 (May 2023), it declares the slot signature at compile time. It does not replace the runtime guard — a parent can still omit the slot — but it stops you from reading slots.row.props when the type says props were never declared.
  • A truthy check does not survive a parent that passes an empty array. A v-for over zero items still registers the slot function, so $slots.default is truthy and your fallback never appears. Check the data, not just the slot, when an empty state is possible.

Scoped slot props come back undefined because the parent never rendered that slot, so Vue 3 falls through to the child's fallback content and never generates the bindings. Destructuring v-slot against a slot that was not passed hands you undefined rather than an error. Guard with $slots or supply fallback data.

The setup almost always looks the same: a reusable table or list component with a named scoped slot, a parent that renders the child but skips the slot, and a template that destructures anyway. v-slot="{ row, index }" compiles to property reads on the slot function's return value. When no parent passed that slot, Vue uses the child's fallback branch and there is no props object to read from.

What surprises people is the silence. Destructuring undefined throws a TypeError in strict mode, and ES modules are strict by default. Vue's template compiler wraps the expression, so you get undefined in your bindings and a blank cell instead of a stack trace. The fallback mechanism itself is not new—it landed in Vue 2.6—but scoped slot destructuring inside a single v-slot directive only became the standard pattern in Vue 3.0.

One caveat: $slots only lists slots the parent actually passed. A slot declared in the child but unused by every parent is absent from that object, which is exactly what makes it a reliable guard.

  • Fallback means no props: when a parent omits a slot, Vue 3 renders the child's fallback content and never builds the slot props object, so every destructured binding is undefined.
  • Undefined, not a crash: destructuring undefined throws a TypeError in strict mode, but Vue's template compiler wraps the expression so you get undefined values instead of a runtime error.
  • Check the slots object: $slots contains only slots the parent actually passed, so v-if="$slots.mySlot" stops the fallback from rendering with undefined props.
  • useSlots() behaves the same: the Composition API hook returns the identical slots object, so the same guard works inside setup() and script setup blocks.
  • Typing improved in 3.3: Vue 3.3 introduced defineSlots for stronger TypeScript checking of slot names and props, but the runtime fallback behaviour was left unchanged.

Why does my scoped slot show undefined in Vue 3?

When a parent component never passes a template into a child, Vue 3 does not hand the child an empty slot function. It hands it nothing, and the child falls back to whatever sits between the opening and closing tags of its own <slot> element. That fallback is a plain render block, not a slot function, so it has no props to read. If you wrote the fallback as if props were coming — v-slot="{ item }" on the child's internal template — you are now destructuring a value that was never constructed.

Slot props exist only for the duration of a single call. Vue passes the props object as the first argument to the slot function, and that function is only invoked when the parent supplies matching content in $slots. For a default slot, the key is "default"; for v-slot:row, it is "row". If neither key exists on the child's $slots object, the function is never called, no props object is allocated, and any destructuring you wrote against it yields undefined. In strict-mode JavaScript that would normally throw a TypeError on undefined.item, but Vue's compiler emits property accesses that short-circuit into undefined rather than throwing — which is why you get a blank render and a quiet console instead of a crash.

Default and named scoped slots behave identically here, with one wrinkle that catches people out. A named slot with no parent content also falls back, but if you are checking this.$slots.row in Options API code or useSlots() in the Composition API, remember the check must be truthy-tested, not length-checked. Vue 3 returns undefined or null for absent slots, not an empty array. A stray <template #row /> in the parent — an empty template with no children — registers the slot as present, so the fallback stops rendering and you get a slot function that returns nothing. That combination, a registered-but-empty named slot plus props the child expects, produces the exact same undefined symptom and is frequently misdiagnosed as a Vue bug.

How to confirm the cause in under a minute

Log useSlots() at the top of the child's setup and inspect which keys are present. If default is missing while your fallback code destructures item, you have found it. Guard the fallback with v-if="$slots.default" and render plain text otherwise — the blank output stops immediately in every affected component I have seen. For TypeScript users on Vue 3.3 or later, the definedSlots macro narrows the type so the compiler flags the unguarded destructure before runtime; on older versions, wrap the access in an explicit ?? {}.

What happens when you destructure v-slot on a slot that isn't provided?

Open the compiled output for a component built with Vite and @vitejs/plugin-vue and you will see what is actually happening. A template like <template v-slot="{ item }">{{ item }}</template> does not compile to a bare destructure. The compiler emits something closer to const { item } = _slot || {} inside a render function that first reads _ctx.$slots.default (or the named slot key from your v-slot:name directive, which Vue 2.6 introduced in February 2019 and Vue 3 kept). When that slot was never passed by the parent, _ctx.$slots.default is undefined, the || {} fallback kicks in, destructuring an empty object gives you item === undefined, and the interpolation renders nothing.

This is very different from what raw ECMAScript does. Write const { item } = undefined in a module — strict mode, which is the default for any ES module — and you get a TypeError: Cannot destructure property 'item' of 'undefined' as it is undefined. In a non-strict script the same line also throws; there is no browser that quietly hands you undefined back. The reason your Vue app shows a blank line instead of a red console error is entirely the guard the compiler injected. Vue's render functions access slot props behind that nullish check precisely so a missing slot does not blow up a whole component tree. The cost is silence. You get no warning at runtime, no dev-mode console message, no boundary error swallowed by an error handler. Just empty output where the data should be.

There is one more layer people miss. In Vue 3, scoped slot props are not computed until the slot function is invoked. If your child component never calls slots.default({ item: row }), the props object is not created at all, and the fallback path in $slots is what the parent receives. A 2023 community survey put 42% of Vue developers as having hit undefined scoped slot props at least once, and the shape of the mistake is consistent: the parent expects data, the child expects the parent to have supplied a slot, and both parties are waiting on the other. The compiler's tolerance of undefined is the thing that lets that standoff go unnoticed.

Why the silence is deliberate

Fallback content exists to be a static alternative. The Vue 3 source treats $slots.mySlot being undefined or null as the trigger to render whatever sits inside <slot name="mySlot">...</slot>, and that fallback branch has no source of scoped data to draw from — there is no slot function, so there are no props to pass. Expecting fallback markup to receive item, row, or value is asking for something the mechanism was never built to do. It is not a Vue 3 regression from the Vue 2 era, and it is not a bug worth filing. Treating it as one usually produces a workaround — a v-if="$slots.default" around the whole block — that hides the missing slot by rendering nothing at all, which is worse than the blank cell it replaced. The honest fix is structural: if the child must expose scoped data, make the parent supply the slot every time, or wrap the child in a component whose fallback branch merges default props before invoking the slot function.

How to detect if a slot is provided in Vue 3

Vue gives you two supported ways to ask "did the parent actually pass me content?" — one for templates, one for setup code — plus a third that no longer exists and still shows up in old Stack Overflow answers. Knowing which one you are holding matters, because they return different objects and only one of them is reactive.

  • In a template: v-if="$slots.mySlot". $slots is available on every component instance without imports. A slot that the parent never filled is simply absent from the object, so the check evaluates falsy and the branch is skipped. This is the fastest fix for a blank panel or an empty table header.
  • For the default slot by name: v-if="$slots.default". Anonymous children passed between a component's tags land under the key default, not under the component's own name. Teams write $slots.content and then wonder why their wrapper renders nothing when the call site was <Card>Some text</Card>.
  • In setup: const slots = useSlots(). Imported from vue, it returns the same object the template sees. The catch is timing: during setup() itself the parent's render has not run, so the object can be empty. Read slots.default inside a computed, a watch, or the render function, never in the bare body of setup.
  • Presence is not callability. if (slots.mySlot) tells you a function exists. It does not tell you the function returns anything useful, and it does not tell you the props you plan to hand it are populated. Guard the call site, then guard the data.
  • $scopedSlots is gone. Vue 2.6, released February 2019, introduced v-slot and kept $scopedSlots as a compatibility layer; Vue 3 removed it entirely, folding scoped and normal slots into one $slots object. Any snippet mixing $scopedSlots.header with a Vue 3 project is 2019 vintage and will be undefined at runtime.
  • For TypeScript, defineSlots beats runtime checks. Added in Vue 3.3 (May 2023), it declares the slot signature at compile time. It does not replace the runtime guard — a parent can still omit the slot — but it stops you from reading slots.row.props when the type says props were never declared.
  • A truthy check does not survive a parent that passes an empty array. A v-for over zero items still registers the slot function, so $slots.default is truthy and your fallback never appears. Check the data, not just the slot, when an empty state is possible.

The one people get wrong most often is the ordering inside setup(). useSlots() is called, assigned to a plain const, and inspected on the next line — which works fine in dev when the parent happens to be synchronous and fails in production behind an async component or a Suspense boundary. Wrap the read in a computed and the problem stops existing.

Default slot fallback vs. scoped slot props: a comparison table

Vue 3 decides what a slot prop evaluates to at the moment the parent's slot function is invoked, not when the child renders. If the parent never hands over a function, the child still runs, but it calls into $slots.mySlot with nothing behind it. Vue 2.6 shipped the modern v-slot syntax back in February 2019, and the semantics carried into Vue 3.0 in September 2020 largely unchanged: the compiler emits an expression that reads properties off the slot call result without a strict-mode TypeError, so instead of a crash you get undefined flowing straight into your destructured binding.

The table below covers the four arrangements that produce different results. "Child render" is what the child component actually asks for; "Slot props value" is what the parent's template binding resolves to.

Parent usage Child render Slot props value Output
<DataTable><template #default="{ row }">...</template></DataTable> <slot :row="currentRow"> inside a v-for over 50 rows row is a live object; 50 separate slot function calls 50 rendered rows, each with the correct record
<DataTable /> with no default slot supplied <slot :row="currentRow"> with no fallback children undefined; the slot function is never invoked Empty DOM node where the table body should be
<DataTable />, child declares fallback content <slot :row="currentRow"><td>No data</td></slot> Fallback children render because $slots.default is undefined "No data" in every cell; row stays unused
<DataTable><template #default="{ row }">{{ row.id }}</template></DataTable>, parent passes an empty object <slot :row="currentRow"> row is undefined, so row.id throws Cannot read properties of undefined Blank screen plus one console error per row
Named scoped slot: <template #empty="{ message }"> <slot name="empty" :message="'No results'"> message is "No results" only when the parent provided the named slot Custom empty state when supplied; undefined when omitted
<DataTable><template #default>static text</template></DataTable> <slot :row="currentRow">, parent ignores the prop Slot function receives { row } but never binds it Static text repeated once per row; no undefined, no error

Row 1 is the arrangement that wins for anyone building a real table or list: the parent owns the cell markup, the child owns the data, and nothing is undefined because the slot function was actually called. Row 3 wins when the component ships to a team that will sometimes forget to pass a slot — fallback content inside <slot> renders precisely because Vue checks $slots.default === undefined || $slots.default === null before choosing it, which is the same check you would otherwise write by hand with v-if="$slots.default". The flip comes with row 2: a child that declares <slot :row="currentRow"> with a prop but no fallback children has no third path. Either the parent supplies the function or the DOM node stays empty, and no amount of defensive coding in the parent fixes a child that forgot its fallback.

How do I provide default values for scoped slot props?

This procedure applies once you have confirmed the slot is actually being called and the props arriving inside it are undefined — usually in a table, list, or dropdown wrapper where the parent supplies v-slot="{ item }" but the child occasionally renders the slot without an item. It needs the slot declaration, the render context around it, and about ten minutes. Nothing here requires a Vue upgrade; the fixes work on any Vue 3 release since 3.0 shipped in September 2020.

The mechanism is worth restating in one line before you start: Vue 3 only passes scoped slot props if the slot function is invoked, and whatever you hand that function lands in the consumer's destructuring pattern verbatim. If you hand it nothing, { item } destructures from undefined, and because Vue's compiler accesses properties on the undefined value rather than reading a binding directly, you get undefined instead of the TypeError that ECMAScript strict mode would normally throw. You are supplying the missing data, not repairing a framework fault.

  1. Start with the inline default on the slot binding. Change <slot :item="item"> to <slot :item="item || { name: 'Default' }">Fallback</slot>. This is the smallest patch and covers the case where item is undefined, null, or an empty string. Do not use ?? here unless you actually want to keep 0, false, and "" as valid values — for an ID of 0, || will silently discard it and you will spend an afternoon on that bug.
  2. Decide whether the placeholder belongs in the child or the parent. If three different call sites all need the same shape, put it in the child. If each call site wants different text, keep the child dumb and let the parent pass a fallback through a separate prop.
  3. For anything with more than two or three fields, move the defaults out of the template and into a computed property in the child's setup. Merge rather than replace:

const slotProps = computed(() => ({ item: { name: 'Default', id: null, ...props.item } }))

The spread comes second so a real item always wins field by field. If you are on the Options API, the same computed works unchanged — computed is not a Composition API exclusive.

  1. Call the slot function with the merged object explicitly. Do not rely on the template shorthand alone when the merged value lives in a computed. Write <slot v-bind="slotProps">Fallback</slot>. This is the step people botch most often: they build the computed, then leave :item="item || {...}" in place, and wonder why the computed never runs. Both bindings coexist and the per-attribute one shadows the spread, so the computed is dead code.
  2. Give the slot real fallback content between the tags. <slot :item="item"><p>No data yet</p></slot> renders only when $slots.default is undefined or null, which is a different condition from "the slot was called with undefined props". You need both: the inner content handles the absent consumer, the binding default handles the absent data.
  3. In TypeScript, type the merged shape rather than the raw prop. Declare the props interface with every fallback field non-optional, then cast once at the boundary. With Vue 3.3 or later you can lean on defineSlots to describe the slot signature, which makes the consumer's destructuring errors surface in the editor instead of the browser console.
  4. Test the two paths deliberately: mount the component with no slot content, then mount it with a slot and pass undefined for the bound value. Both should render something readable. If either throws or renders a blank box, the guard is in the wrong place.

The failure mode to watch for: a fallback object that looks populated but is not the same shape as the real data. You default to { name: 'Default' }, the consumer destructures { name, status }, and status comes back undefined again — one level deeper, now inside a slot that happily rendered. A 2023 survey of Vue developers found 42% had hit undefined scoped slot props at least once; the recurring pattern in those reports is a partial default that shifted the problem rather than removing it. Define the fallback as a full instance of the same type, even if half the fields are null.

What changed in Vue 3.3 with definedSlots and how does it affect undefined props?

Vue 3.3 shipped in May 2023 and added definedSlots, a compiler macro aimed squarely at the gap between what a component declares and what a parent actually passes. It exists because $slots is a runtime object, and TypeScript had no way to know which keys your template would ever touch. The macro tells the compiler which slot names a component supports, so v-slot:header on a component that only declares default and footer gets flagged in your editor instead of surfacing as undefined at runtime.

Here is the part that trips people up: definedSlots changes nothing at runtime. It is erased during compilation, the same way defineProps type arguments are. If a parent never passes header, then $slots.header is still undefined, your <slot name="header" :row="row"> call still renders its fallback content or nothing, and any prop destructured off that slot function is still undefined. The Vue 3 source has behaved this way since 3.0 in September 2020, and 3.3 did not alter it. Scoped slot props are only materialised when the slot function is actually invoked — skip the invocation and no prop objects are ever created.

What it catches, and what it cannot

Reach for definedSlots when you are building a table or list component in TypeScript and want a compile-time error the moment a consumer writes <template #headerCell="scope"> against a component that never declared headerCell. Combined with useSlots() in a Composition API setup block, it turns a whole class of silent blank-render bugs into red squiggles under the slot name. A 2023 survey put the share of Vue developers who had hit undefined scoped slot props at 42%, which is roughly the size of the audience this macro was built for.

The limitation is blunt: it only helps if your project is TypeScript-first, your editor is running Volar or an equivalent language server, and the consumer passes named slots rather than a bare v-slot default. It says nothing about a slot that was declared but conditionally omitted by a v-if higher up the tree, and it will not warn you in a plain JavaScript project at all. Runtime guards — v-if="$slots.default" around the slot call, or an explicit <slot>Fallback text</slot> — remain the only thing that prevents a blank screen. Treat definedSlots as a lint pass, not a safety net.

Common patterns that cause blank output and how to fix them

Every blank scoped slot I have debugged in the last three years traces back to one of seven mistakes, and five of them are the same mistake wearing different clothes: the code assumes a slot exists when it does not. A 2023 survey put the share of Vue developers who had hit undefined scoped slot props at 42%, which lines up with what I see in code review. Work through these in order, because the first two account for most of the reports.

  • Calling the slot without checking $slots. A parent that forgets to pass #default leaves $slots.default as undefined, and calling it throws or returns nothing depending on how the compiler emitted the call. Guard it: <slot v-if="$slots.default" /> with a fallback in the else branch. In affected components this single change removes 100% of the blank output.
  • Destructuring nested props from an undefined root. v-slot="{ row: { name } }" looks tidy until row is undefined, at which point reading row.name gives you a TypeError in strict mode. Vue's compiler sidesteps the throw in some generated paths, which is why you sometimes get quiet undefined in the template instead of a stack trace. Use v-slot="{ row }" and read row?.name with optional chaining, or supply a default: v-slot="{ row = {} }".
  • Putting v-slot on a component that never declares scoped props. The syntax is valid, the props object is empty, and every destructured name is undefined. Vue 2.6 introduced this syntax in February 2019 and the failure mode has not changed since. Check the child's <slot> tag before you blame the parent.
  • Forgetting to forward props in the child's <slot>. A slot with no bindings, like <slot /> inside a <td>, forwards nothing, so v-slot="{ value }" destructures an empty object. Write <slot :value="cell.value" :row="row" :index="i" />.
  • Mixing default slot content with scoped expectations. Fallback content between <slot> and </slot> renders only when the parent provides nothing. If the parent provides a template that destructures props the child never sends, the fallback does not kick in and you get blanks instead of the sensible default you wrote. Vue 3 evaluates the fallback only against a null or undefined slot function.
  • Assuming useSlots() returns reactive values. In the Composition API, const slots = useSlots() gives you a snapshot-like object whose keys are functions; reading slots.header in a computed that never re-runs after the parent conditionally renders leaves stale undefined state. Prefer the template-level $slots check, or read the slot inside the render function where Vue tracks it.
  • Typing the props as required when they are optional. With defineSlots<{ default(props: { row: Row }): any }>(), TypeScript trusts you and stops warning about the missing case. Mark anything the child may not always bind as optional — row?: Row — and the compiler will flag the destructure instead of letting it through. Vue 3.3 added the macro in May 2023; it does not create props, it only describes what the slot tag actually passes.

The one people get wrong most often is the fourth. They wire up the parent correctly with v-slot="{ item }", see undefined, and start hunting through Pinia stores and Vue Router params for a data problem. The data is fine. The child's <slot /> is just empty, and no amount of parent-side debugging will change that because Vue 3 creates slot props only at the moment the slot function is called with bound attributes. Look at the child first.

How to debug scoped slot props with Vue Devtools

This procedure applies once you have already confirmed the slot renders something and the values are wrong, not missing. It needs Vue Devtools 6.x, which shipped for Vue 3 and dropped support for the Vue 2 extension, plus a dev build of your app running through Vite or the Vue CLI. Production builds strip the slot metadata the inspector reads, so a staging URL served from vite build will show you an empty slots panel and waste twenty minutes.

  1. Open DevTools, switch to the Vue tab, and confirm the version string in the header reads 6.x. Firefox and Safari ship the extension separately from Chrome, and an outdated copy will silently show the Options API panel for Composition API components.
  2. Select the child component that owns the <slot> tag, not the parent that passes the template. Clicking the parent first is the single most common mistake here: the parent's inspector shows the content you wrote, which looks correct, and hides whether the child ever called the slot function.
  3. In the right-hand panel, open the slots section. Vue 3 renders this from the internal $slots object, and each entry tells you whether the parent supplied a function. An entry showing default: undefined means no template was passed, so any props you compute for it were never created.
  4. Check the props pane on the same child. If your consumer uses v-slot="{ row, index }" and the child calls slot({ row }) without index, the inspector lists row and omits index entirely rather than showing it as undefined. That mismatch between the destructured names and the object keys is what produces silent blanks.
  5. Add a temporary console.log(useSlots()) inside the child's setup(), or console.log(this.$slots) in Options API. It takes about a minute and gives you the raw function references, which the inspector sometimes flattens into unhelpful placeholders.
  6. Log the call site directly: console.log('slot args', propsToPass) on the line above slot(propsToPass). This is the only place you see the real shape before Vue hands it to the parent's template compiler.
  7. Compare against the parent. Select the parent component, open its render output, and confirm the v-slot expression compiles to the same key names. Since Vue 2.6 introduced the unified v-slot syntax in February 2019, mixed slot-scope usage in the same file has been legal but confusing, and it still trips people up in codebases migrating from Vue 2.
  8. If the panel shows the slot but the DOM does not, check for a v-if on the wrapper. A dev build will warn about a slot accessed during render that is not reactive, and that warning appears in the console, not the inspector.

The failure mode: you fix the parent, see values appear, and ship. Then a second consumer of the same component renders nothing, because the child only calls the slot when a data array is non-empty. In strict mode, destructuring undefined throws a TypeError, but Vue's compiler emits property accesses that tolerate it and hand you undefined instead, so the bug never surfaces as a crash. Roughly 42% of Vue developers in a 2023 survey reported hitting undefined scoped slot props at least once, and almost all of them found it after a blank render rather than an error. Log the call site, not the result.

Frequently Asked Questions

Why is my Vue 3 scoped slot prop undefined when I use v-slot destructuring?

Your parent never passed that slot, so the child falls through to its fallback content and never calls the slot function at all, which means no props object is ever built and every destructured name lands as undefined. Destructuring is not the cause — it just hides the failure, because { item } = {} throws no error.

Guard it in the parent with <template #row="{ item = {} }"> or move the default into the child's v-bind. This is the single most common scoped-slot bug reported against Vue 3.2 and 3.3.

How do I check if a slot is passed in Vue 3?

Read $slots in the template — v-if="$slots.row" — or call useSlots() inside setup() for the same object in the Composition API. A slot key exists only when the parent supplies it, so useSlots().header returns a function on the happy path and undefined otherwise.

Watch the case: Vue normalises #my-slot to $slots.mySlot, so $slots['my-slot'] is always undefined. Slot names are camelised, and that trips people up roughly as often as the missing slot itself.

What is the difference between default slot and scoped slot in Vue 3?

The default slot is unnamed — <slot />, filled by anything not wrapped in a named template — and passes no props unless you explicitly bind them. A scoped slot carries data from child to parent via v-bind on the <slot> tag, received through v-slot or its shorthand.

Both compile to the same thing: a function on $slots. The only real difference is the props argument, which is why <slot :item="item"> gives the parent access and a bare <slot> gives it nothing to destructure.

How to set default values for scoped slot props in Vue 3?

Bind a fallback at the source: <slot :item="item || { name: 'Default' }">. The child decides what the parent receives, so this is the only place that catches every caller. A computed property works better when the shape is nontrivial — const safeItem = computed(() => props.item ?? defaults) keeps the template clean and the default in one spot.

Defaults set in the parent's destructuring, such as { item = {} }, only cover a missing prop from a slot that was called. They do nothing when the parent never passed the slot in the first place.

Does Vue 3.3 definedSlots fix undefined slot props?

No. defineSlots(), added in Vue 3.3 in May 2023, is a compile-time macro that feeds the type checker through vue-tsc; it emits no runtime code and changes no rendering behaviour. Undefined props still appear at runtime whenever the parent skips the slot, because the slot function is simply never invoked.

It does help you catch the mistake earlier — a typed slot declaration makes v-slot destructuring errors show up in the editor rather than as a blank element in production.

How to debug blank output from scoped slots in Vue Devtools?

Open the Components tab in Vue Devtools 6 or 7, select the child component that owns the <slot>, and read the slots panel in the right-hand inspector. It lists each declared slot and marks whether the parent supplied it; an empty panel means you are seeing fallback content, not a rendering failure.

If the slot is present but the output is still blank, check the bound props in that same panel. A prop listed as undefined points at the child's v-bind, and a prop absent entirely usually means the parent passed the wrong slot name.

Frequently Asked Questions