skip to content

In Vue 3, how does `<DataTable>` expose each row to a parent's scoped cell slot, and why can't slot content read the table's own variables?

level: middleimportance: must knowfreq 58%

answer

  1. whose template the content belongs to
  2. lexical scope of templates
  3. attributes on the outlet
  4. destructure in v-slot
  5. slots compile to functions

basics

~20 s

Vue slot content is compiled in the parent's scope, so it cannot see the child's variables. The DataTable passes data as attributes on the outlet, <slot name="cell" :row="row" />, and the parent receives them with <template #cell="{ row }">.

solid answer

~40 s

Slot content is part of the parent's template, so it follows the parent's **render scope**: it can read the parent's state but not the child's, the same way JavaScript closures see only their lexical scope. To give the content access to per-row data, the child passes **slot props** as attributes on the outlet: `<slot name="cell" :row="row" :column="col" />` inside its `v-for`. The parent receives them as the value of `v-slot`: `<template #cell="{ row, column }">`, with ordinary destructuring, renaming and defaults. The outlet's `name` is reserved and never appears in the props. Under the hood a scoped slot compiles to a function the child calls with those props, once per row. One compile rule: when you mix a scoped default slot with named slots, the default one must use an explicit `<template #default>`.

go deeper

for a junior

Know that slot content sees the parent's data, and that a child shares its own data by binding attributes on <slot> and reading them with #name="{ ... }".

for a middle

Explain render scope, the reserved name attribute, destructuring in v-slot, and why mixing a scoped default slot with named templates needs an explicit #default.

for a senior

Design a table or list API around scoped slots with sensible fallbacks, and use the slot-as-function model to reason about when slot content re-renders.

for a principal

Decide how much rendering a shared data component delegates through scoped slots versus configuration props, weighing flexibility against a consistent look.

## Render scope: whose variables slot content sees In Vue, **every expression in a template is evaluated in the scope of the component whose template it is written in**. Slot content is written in the parent's template, so: - it can read the parent's refs, props and functions; - it cannot read the child's local state, even though it is *displayed* inside the child. The docs put it as: expressions in the parent template only have access to the parent scope; expressions in the child template only have access to the child scope. That is why a parent writing `{{ row.total }}` inside `<DataTable>` gets an undefined `row`: `row` is the loop variable of the table's own `v-for`, which the parent never sees. ## Scoped slots: the child passes props to its outlet A **scoped slot** lets the child hand data to the parent's fragment. The child writes attributes on the `<slot>` outlet, like props on a component: ```vue <!-- DataTable.vue --> <script setup lang="ts"> defineProps<{ rows: Record<string, unknown>[] columns: { key: string; label: string }[] }>() </script> <template> <table> <tr v-for="(row, i) in rows" :key="i"> <td v-for="col in columns" :key="col.key"> <slot name="cell" :row="row" :column="col">{{ row[col.key] }}</slot> </td> </tr> </table> </template> ``` The parent receives them as the value of `v-slot`: ```vue <DataTable :rows="orders" :columns="cols"> <template #cell="{ row, column }"> <strong v-if="column.key === 'total'">{{ row.total }} EUR</strong> <span v-else>{{ row[column.key] }}</span> </template> </DataTable> ``` Details worth knowing: 1. The `v-slot` value behaves like a **function parameter**, so it supports destructuring, renaming (`{ row: order }`) and defaults (`{ column = {} }`). 2. The outlet's `name` attribute is **reserved** and excluded from the props; the parent here receives `{ row, column }` only. 3. `v-bind="obj"` on the outlet spreads an object's fields as slot props. 4. The content between the outlet tags is still fallback content, rendered for callers that pass no `#cell` fragment. ## What a scoped slot compiles to The Vue docs describe a scoped slot as a **function passed into the child**. The parent's fragment becomes something like `cell: ({ row, column }) => [/* vnodes */]`, and the child's outlet calls it with the current props each time it renders. That model explains the behaviour: - the fragment runs once per row, with that row's data; - the parent's variables are still available, because the function is created in the parent's scope, like a closure; - the content is re-evaluated whenever the child re-renders that outlet. ## Mixing a scoped default slot with named slots When a component has both a scoped default slot and named slots, `v-slot` on the component tag itself is ambiguous: would its props be visible in the named fragments? Vue refuses to guess. Putting `v-slot="{ item }"` on the component and a `<template #footer>` inside is a **compile error** (`Mixed v-slot usage on both the component and nested <template>`). Use an explicit `<template #default="{ item }">` next to `<template #footer>`, which makes it obvious that `item` exists only inside the default fragment. ## Summary | Question | Answer | |---|---| | Can slot content read parent state? | Yes, it is written in the parent's template | | Can it read the child's state? | Only what the child passes as slot props | | How does the child pass data? | Attributes on `<slot>` | | How does the parent receive it? | The value of `v-slot`, often destructured | | Is `name` a slot prop? | No, it is reserved |

  • How can a Vue 3 `<DataTable>` offer a separate scoped slot per column, such as `#cell-total` and `#cell-status`?
    Give the outlet a dynamic name: `<slot :name="'cell-' + col.key" :row="row">`. The parent then writes `<template #cell-total="{ row }">`, and can choose a name at runtime with dynamic arguments, `<template #[slotName]="{ row }">`. Callers only override the columns they care about; others render the outlet's fallback.
  • Why does Vue 3 refuse to compile `v-slot` on the component tag together with a named `<template>` slot?
    The props of a default slot declared on the component tag would look as if they were in scope inside the named fragments, but they are not. Vue reports mixed `v-slot` usage at compile time; use an explicit `<template #default="...">` so each fragment's props are clearly scoped to it.

saying these in an interview costs you the question

  • Slot content can read the child component's variables because it renders inside the child.
  • The slot's name attribute is passed along as one of the slot props.
  • Scoped slot props are two-way bound, so assigning to row updates the child.
  • A scoped default slot can use v-slot on the component alongside named templates.
  • Scoped slot props must be declared in the child's defineProps.