skip to content

Composition, Slots & Reuse

Building bigger components from smaller ones without inheritance: default, named and scoped slots, wrappers, reusable units of stateful logic. Asked because reuse choices expose design taste.

on this pageshow

questions

5

In a component framework, what does a default content slot do, and who owns the content that lands in it?

level: juniorimportance: must knowfreq 78%

answer

  1. a hole the caller fills
  2. frame from the component, content from the caller
  3. names resolve in the caller's scope
  4. fallback when nothing is passed

basics

~20 s

A default content slot is a placeholder where a component renders whatever content its caller passed between its tags. The caller writes and owns that content; the component only chooses where, and whether, it appears.

solid answer

~50 s

A **default content slot** is a marked position in a component's own markup that renders whatever the caller wrote between the component's tags. The distinction that matters is ownership: the projected markup is declared in the *caller's* scope, so its bindings, handlers and names resolve there, and its identity survives the receiving component re-rendering. The receiving component decides placement only — where the content sits, what chrome wraps it, whether it renders at all, and what fallback shows when the caller passed nothing. It should not try to read or rewrite the content's internals, because that couples it to markup it does not own. This is the containment half of composition: a generic frame such as a card, dialog or page layout takes content, instead of inheriting from a base component or accepting a pre-rendered markup string.

go deeper

for a junior

Recall that content written between a component's tags renders at its default slot and belongs to the caller. Know that a component can supply fallback content for the case where nothing was passed.

for a middle

Explain scope: the projected markup's bindings and handlers resolve in the caller, while placement, chrome and conditional rendering belong to the receiving component. Say why a markup string input is worse.

for a senior

Show the judgment: when a component should take content versus take data, and how you stop a generic frame from growing one boolean input per caller.

for a principal

Frame it as API surface. Every region you publish is a compatibility promise about structure, so weigh that against the god-component alternative and decide how much layout your callers own.

## What a default content slot is Most of a component's markup is fixed: it describes the structure that component always renders. A **default content slot** is the one position in that markup where the framework renders something the component did not write — whatever the caller placed between the component's opening and closing tags. The component ships a frame; the caller ships what goes in it. This is **content projection**, and the unlabelled region that takes the caller's content with no further naming is the **default** slot. The difference from an ordinary input is what travels. An input carries **data**: a string, a number, an object. A slot carries **markup**: a live subtree with its own elements, child components, bindings and event handlers, created and updated by the framework. ## Who owns projected content - **The caller declares it**, so every name inside it resolves in the caller's scope — the caller's state, the caller's handlers, the caller's components. The receiving component cannot rebind those names. - **The caller's data drives it.** When the caller's state changes, the projected markup updates, even though it renders inside someone else's frame. - **Its identity is stable.** When the receiving component re-renders, the projected subtree is the same subtree as before rather than a fresh copy, so anything stateful inside it keeps its state. - **The receiving component has no portable way to read inside it.** Some runtimes expose a list of projected entries, but inspecting or rewriting them couples the component to markup it does not own; treat the content as opaque. ## What the receiving component still decides 1. **Placement** — where in its own structure the content appears. 2. **Chrome** — the border, padding, heading, ARIA roles and landmarks it wraps around the content. 3. **Whether it renders at all** — the slot can sit inside a conditional, a disclosure, an inactive tab. 4. **What to show instead** — **fallback content**, rendered when the caller passed nothing. Whether the same slot may be rendered in more than one place, or more than once, differs by runtime: treat that as a per-runtime rule rather than an assumption, and when you genuinely need the content more than once, take it as a parameterised block instead. ## Containment versus specialization | | Containment (projection) | Specialization (fixed inputs) | |---|---|---| | What varies per caller | the markup supplied | the values supplied | | What the component knows | where content goes | what the content means | | Public surface | slot names | input names | | Fits | frames: card, dialog, panel, page layout | roles: money field, avatar, status badge | | Fails when | the caller needs the component's own data | every new case adds another boolean | Both are composition; they differ in which side owns the markup. A dialog that takes a title region, a body region and a footer region is containment — it has no opinion about what a footer contains. A confirmation dialog built on that dialog, with a fixed title, a message input and two buttons, is specialization: it has an opinion, and it expresses that opinion by **using** the generic dialog, not by inheriting from it. ## Why projection replaced inheriting from a base component - Subclassing a component to inject markup requires knowing its internal structure, and that structure changes. - A base component that must anticipate every subclass grows extension points nobody else needs — the fragile base class problem in component form. - A slot makes the extension point **named and narrow**: the only promise is that content you give me renders here. - The component's public surface stays the same three things — inputs, outputs, slot names — for every consumer. The other tempting shortcut is an input carrying pre-rendered markup as a string. That forces the receiving component to insert raw markup the framework cannot track: no bindings, no handlers, no child components, no teardown, and an injection hazard for anything a user typed. Projected content avoids all of it because the framework created the subtree in the first place. ## Where runtimes differ Frameworks agree on the ownership rule and differ in mechanics. A runtime that re-runs a component function on every state write hands the already-created content in as a value the component positions. A runtime with fine-grained tracking compiles the projected region into its own reactive block that stays attached to the caller. A compile-time runtime resolves slots when it builds the component, so an unfilled slot can be eliminated outright. None of that changes the contract: the caller writes the content, and the component decides where it lands.

  • What should a component render at its default content slot when the caller passes nothing?
    Its declared fallback content, or nothing at all — and often not the surrounding chrome either, because an empty bordered panel with a heading reads as a bug. Runtimes let a component detect the empty case, which is the difference between a graceful default and a stray frame on screen.
  • Why is projecting content better than accepting pre-rendered markup as a string input?
    A string must be inserted as raw markup, which the framework cannot track: no bindings, no event handlers, no child components, no teardown, and an injection risk for anything user-supplied. Projected content stays a live subtree the framework created and the caller owns, with its handlers and updates intact.

saying these in an interview costs you the question

  • Thinks the receiving component creates and owns the projected content
  • Expects projected markup to resolve names in the receiving component's scope
  • Passes markup as an HTML string input instead of projecting content
  • Believes a component must subclass a base component to add markup
  • Assumes the receiving component may freely inspect and rewrite projected nodes
open as a page

Two components call the same reusable stateful logic unit; what do they share, and what does each get its own copy of?

level: middleimportance: must knowfreq 72%

basics

~10 s

They share the code, not the data. Each call site gets its own state, derived values, effects and cleanup, owned by the calling component instance, so one caller's updates never reach the other's copy.

open as a page

What must a wrapper component forward so that callers of the component it wraps cannot tell the wrapper is there?

level: middleimportance: should knowfreq 48%

basics

~20 s

Everything it does not consume: inputs and attributes it does not recognise, the caller's event bindings, projected content in the same named regions, and a handle to the underlying element. The wrapper adds only its own behaviour.

open as a page

When projected content needs a value only the receiving component has, such as the current row, what does a scoped slot do?

level: middleimportance: should knowfreq 52%

basics

~10 s

A 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.

open as a page

A component now sits under five wrappers added one per variation; how do you decide between projecting content, wrapping, and extracting a reusable unit?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Match the tool to what varies: project content when the difference is markup, wrap when the difference is behaviour around a whole subtree, and extract a reusable stateful unit when the difference is state and effects several components need separately.

open as a page