You maintain a design-system library of custom elements. How do you decide what a component exposes as a named <slot> versus what it renders itself, and what does publishing a slot name commit you to?
answer
- responsibility decides the line
- guarantees stay inside
- names are public strings
- renames break silently
- place content, cannot transform it
basics
~20 sSlot what the caller must own — arbitrary content whose markup you cannot predict; render yourself what the component is responsible for, such as structure, layout and accessibility wiring. A published slot name is public API: renaming one makes consumer content vanish silently.
solid answer
~60 sThe dividing line is responsibility. If the component would otherwise have to re-implement arbitrary markup — a heading, an icon, a body of rich text — expose a slot and let the caller own it. If the component is accountable for it — the wrapper structure, spacing, focus order, the ARIA relationships between regions — render it yourself, because a slot hands away exactly the control you need to keep the guarantee. Each named slot is then a permanent contract: names are matched by string, an unmatched `slot` attribute renders **nothing** with no warning, so a rename is a silent breaking change for every consumer and belongs in a major version. Slots also have hard limits — you cannot validate, transform, reorder or wrap what you are handed, only place it and provide fallback. When you need structured, typed data rather than markup, a property is the better contract. I keep the named-slot count small, ship fallback content for optional regions, and add a development-mode check that warns on any host child whose `assignedSlot` is null.
go deeper
Know the basic split: slots are for content the caller supplies, and the component renders its own structure around them. Named slots are how a component offers more than one such region.
Be able to justify a specific split for a real component and to explain fallback content as the default for optional regions. Note that unmatched slot names fail silently rather than erroring.
Argue the accountability rule — do not slot anything whose correctness you promise, such as accessibility wiring — and treat slot names as versioned public API with silent breakage on rename.
Own the strategy: cap the named-slot surface, define a deprecation path for renames, add development-time unslotted-child warnings, and decide deliberately where markup slots give way to typed data properties.
## The question behind the question Every reusable component draws a line between what it guarantees and what the caller supplies. Slots are the platform's mechanism for that line, and the interview is really asking whether you can place the line deliberately rather than by habit. ## Slot it when the caller must own it Good candidates for a named slot: - **Arbitrary rich content** — a card body, a dialog description. You cannot enumerate the markup, so trying to accept it as a string means either escaping it (limited) or injecting it (an XSS sink you have just created for every consumer). - **Caller-supplied interactive elements** — the action buttons in a dialog footer, where the caller owns the handlers, labels and disabled states. - **Optional decoration with a sensible default** — an icon region whose `<slot name="icon">` carries fallback content that renders until someone overrides it. ## Render it yourself when you are accountable for it Keep inside the component anything whose correctness you are promising: - The wrapper structure and layout, so spacing and responsive behaviour cannot be broken from outside. - Accessibility wiring — the element that carries the role, the relationships between the labelled region and the content. If the caller supplies the node that must be referenced, you cannot guarantee the relationship holds, because you cannot rewrite what you are handed. - Anything with invariants: exactly one close button, a live region that must exist, ordering that the component's keyboard handling depends on. The test that settles most cases: *if a consumer supplies something wrong here, whose bug is it?* If the answer is "mine", do not slot it. ## What publishing a slot name commits you to A slot name is a public string that lives in consumer markup, and the coupling is loose in the worst way: - **Renames fail silently.** Change `slot="title"` to `slot="heading"` and every existing consumer's title simply stops rendering. No exception, no console message, the element still sits in the DOM. Treat renames as breaking changes, and if you must migrate, keep both slots for a release and warn in development. - **Removing a slot is equally silent** from the caller's side. - **Adding a slot is safe**, which makes additive evolution the cheap direction. - **Fallback content is part of the contract too.** Callers come to rely on the default icon or the empty-state text; changing it changes rendered output for people who supplied nothing. Because of this, the named-slot surface deserves the same discipline as a public function signature: documented, versioned, and kept small. A component with nine named slots is usually a layout configuration language in disguise — a sign the component should be split, or that the caller should be composing smaller elements themselves. ## The limits you are accepting Slots place content; they do not process it. Concretely, the component cannot: - **validate** what it received — it can only inspect `assignedElements()` after the fact and complain; - **transform or wrap** it, the way a render-time library can clone a child and inject props; - **reorder** it — rendering follows document order within a slot; - **fully style** it, since slotted nodes remain the page's nodes and inherit the page's rules. If your design depends on any of those, the content probably wants to be data on a property rather than markup in a slot: an array of options a component renders itself keeps every invariant under your control, at the cost of the flexibility that made slots attractive. ## Two techniques worth having in the answer **A development-time audit.** Because unmatched content is invisible rather than loud, add a guard behind a dev flag: ```js for (const child of this.children) { if (!child.assignedSlot) console.warn('[my-card] unslotted child', child); } ``` Run it from the slot's `slotchange` handler. It converts your API's worst failure mode from "nothing renders and nobody knows why" into a message. **Imperative assignment for dynamic routing.** With `attachShadow({ mode: 'open', slotAssignment: 'manual' })`, name matching is switched off and the component calls `slot.assign(node, ...)` itself, choosing at runtime which light children go where. It buys real flexibility — routing by element type, filtering, conditional placement — at the cost of a component that no longer works from markup alone, and it needs a reasonably modern browser (available across the major engines from roughly 2022 onward). It is a good answer for a virtualised or heavily dynamic component and a poor default for a design system, where declarative markup is most of the appeal. ## The summary to say out loud "Slots invert control, and inversion has a price. I slot what the caller must own and keep what I am accountable for. Every name I publish is a permanent, silently-breakable contract, so I keep the set small, ship fallbacks, warn on unslotted children in development, and reach for a data property whenever I need to validate or transform rather than merely place."
- A consumer reports that their slot="header" content disappeared after upgrading your library. What is the most likely cause and how should the release have been handled?The slot was renamed or removed. Name matching is an exact string comparison and unmatched children simply do not render, with no error. It should have shipped as a major version, ideally with both the old and new slot declared for one release and a development-mode warning when the deprecated name is used.
- When would you take content as a property instead of a slot?When the component must guarantee something about it: validating shape, deriving state, reordering, virtualising, or rendering the same data in several places. A property gives you structured data you control end to end; a slot gives you opaque markup you can only place. The cost is flexibility — callers lose the ability to supply arbitrary content.
- What does slotAssignment: 'manual' change, and why is it a poor default for a design system?It disables name-based matching so the component decides assignment itself via `slot.assign(...)`. That enables runtime routing — by element type, or filtering — but the component no longer works from markup alone: content placement now depends on script running, which breaks the declarative, server-renderable story most design systems want.
- How do you keep the number of named slots from growing without limit?Treat every new slot as a request to make the component a layout language. Push back by asking whether the caller could compose two smaller elements instead, whether the region is genuinely caller-owned, and whether an existing default slot plus ordering would do. Slots you add are permanent, so additions deserve the scrutiny of any public API change.
saying these in an interview costs you the question
- Treats slot names as an internal implementation detail
- Slots the elements that carry the component's accessibility guarantees
- Expects to transform or reorder slotted content like framework children
- Assumes a renamed slot produces an error consumers will notice
- Adds a named slot for every visual region a design shows