When projected content needs a value only the receiving component has, such as the current row, what does a scoped slot do?
answer
- content that needs the child's data
- a block with parameters, not a tree
- the component calls the content back
- argument names are public API
basics
~10 sA scoped slot makes projected content a parameterised block: the receiving component invokes it, once or once per item, passing the values only it knows, while the caller still writes and owns the markup.
solid answer
~40 sA plain content slot is a fixed subtree, so it cannot mention data the receiving component computes — the current row, the request state, a validation message — because none of that exists where the caller wrote the markup. A **scoped slot** turns that content into a block with parameters. The caller names the parameters it wants and writes markup using them; the component invokes the block wherever, and as often as, it needs, handing over its own values. Ownership splits the useful way: the component owns iteration, fetching, state and identity, and the caller owns presentation. The cost is that the parameter list becomes public API — rename or drop one and every caller breaks — so pass the smallest set that lets callers render, not a window onto internals.
code
pseudocode · 10 linescomponent DataList(items, rowContent):
for each item, index in items:
# the component owns the loop and the identity
render rowContent(item: item, index: index) keyed by item.id
# the caller owns the markup and names the parameters it reads
render DataList(items: users) with rowContent(item, index):
row:
text "{index}. {item.name}"
button "remove" on click: removeUser(item.id) # caller's own handlergo deeper
Know the shape of the idea: content you pass into a component can take parameters, so the component can hand your markup the item it is currently rendering instead of you guessing at it.
Explain the inversion — the component invokes the caller's block with its own values — and say why a plain slot cannot do it. Name what belongs in the parameter list and what does not.
Design the boundary: the component owns fetching, iteration and identity, the caller owns markup. Show how you keep the parameter set small enough to evolve without breaking callers.
Treat the argument names as a cross-team versioned contract. Decide how such a surface is reviewed and deprecated, since a template-level break shows up at runtime in consumer code, not at your build.
## The limit a plain slot runs into Projected content is written in the caller's scope, which is what makes it useful and also what makes it blind. The caller can only mention things the caller knows. It cannot mention the row a list component is currently rendering, the request state a data component is holding, or the validation message a field component just computed, because none of those exist where the markup was written. A **scoped slot** — also described as a parameterised slot, a content callback, or content with arguments — closes that gap. Instead of a finished subtree, the caller hands over a **block with parameters**. The component invokes the block, passing its own values in, and the framework renders whatever the block produces at the slot's position. ## How the inversion works 1. The component declares that a content region is invoked with arguments — the item and its index, or a state flag and a retry action. 2. The caller writes markup for that region and names the parameters it wants to read. 3. The component invokes the block wherever, and as often as, it needs: once for a wrapper, once per element for a list, again when its own data changes. 4. Names inside the block still resolve in the caller's scope. The **arguments** are the one channel pointing outward from the component. The content is still the caller's, with its own identity, state and handlers. What changed is that it is now a function of values rather than a constant. ## What each shape can express | | Plain content slot | Scoped slot | Inputs only | |---|---|---|---| | Caller supplies | a fixed subtree | a block with parameters | data | | Can mention the component's own data | no | yes, through arguments | not applicable | | Invoked | once, where it is placed | once per invocation the component makes | not applicable | | Owns iteration and state | the component | the component | the component | | Owns markup | the caller | the caller | the component | ## Where the pattern earns its keep - **Lists and tables** — the component owns the loop, the ordering and the identity of each entry, and the caller owns the row's markup. - **Data wrappers** — the component owns fetching, retry and cancellation, and invokes the block with loading, error and data so the caller renders each case. - **Disclosure and selection widgets** — the component owns which panel is open, and the caller renders the trigger using an expanded flag and the toggle action handed to it. - **Field wrappers** — the component owns validation timing, and the caller renders the control and the message. The split is the same every time: the component keeps the behaviour that is hard to get right, and the caller keeps the markup only it can decide. ## The costs - **The parameter list is public API.** Callers write markup against those names, so renaming one is a breaking change that nothing inside the component will flag. - **Passing too much leaks internals.** Handing over the raw response, a cache handle or a node reference invites callers to depend on your implementation. Pass what the markup needs to render, and no more. - **Identity stays yours.** Because the component owns the loop, it must decide what identifies each invocation, usually from an identity value the caller supplies. Callers cannot key a loop they cannot see. - **Cost per invocation.** The block runs once per invocation, so work the caller put inside it repeats at that rate, and the component should invoke no more often than its data actually changed. ## A rule for choosing Ask whether the projected markup needs anything only the receiving component knows. 1. It does not — a plain content slot is simpler for both sides. 2. It does, and the value is one ambient thing every caller needs — consider providing it to the subtree instead, which has its own mechanism and its own rules. 3. It does, and the value differs per invocation, per row or per state transition — a scoped slot is the only one of the three that can express that. ## Where runtimes differ The mechanism has a different surface in each reactivity model. A runtime that re-runs component functions typically passes the block as a plain value the component calls. A runtime with fine-grained tracking compiles the block into a reactive scope re-evaluated when the arguments it reads change, so only the parts touching a changed argument update. A compile-time runtime can resolve the argument names statically and report a misspelled parameter before the code ever runs. What is constant across all of them: the component invokes, the caller renders, and the argument names are a promise you have to keep.
- What should a component pass to a scoped slot, and what should it keep to itself?Pass what the caller needs to render: the item, its index or position, and the state flags the markup branches on such as selected, loading or error. Keep the internals — caches, subscriptions, node references, the raw response shape. Every parameter you expose is a promise you have to keep across versions.
- Who is responsible for list identity when a scoped slot renders each row?The receiving component, because it owns the loop. It decides the key for each invocation, usually from an identity function or field the caller supplies. Callers writing only the row markup cannot see the loop, so leaving keying to them produces silently unstable lists on reorder.
- How is a scoped slot different from just letting the caller pass a component to render?Passing a component to render is close to the same idea with a coarser contract: the arguments become that component's inputs, and the caller loses the ability to write inline markup that closes over its own state. A scoped slot keeps the markup in the caller's template, which is usually why the pattern was reached for.
saying these in an interview costs you the question
- Thinks projected markup can read the receiving component's internal state directly
- Adds a new input for every row variation instead of exposing the row
- Treats the slot's parameter names as a private implementation detail
- Believes a scoped slot moves iteration or state ownership to the caller
- Assumes the projected block runs once rather than per invocation