In a Web Component's shadow DOM, how does a <slot> decide which of the host element's children it renders, and what happens to a child whose slot attribute matches no slot?
answer
- direct children only, never grandchildren
- exact name match, both sides
- no match means nothing renders
- slot children are the fallback
- first matching slot in tree order
basics
~20 sA <slot> without a name takes the host's direct children that have no slot attribute; <slot name="x"> takes the direct children whose slot="x" matches exactly. A child matching no slot is never rendered, silently. An empty slot renders its own children as fallback.
solid answer
~50 sAssignment is a name match between the host's **direct children** and the slots in its shadow tree. A child with `slot="title"` goes to the first `<slot name="title">` in tree order; a child with no `slot` attribute goes to the default slot, the one declared as plain `<slot>`. Deeper descendants are not slottable — only the host's own children are, so you cannot slot a grandchild. Text nodes count as slottable, which is why stray whitespace can make a default slot non-empty. If nothing matches a child — a typo, or no default slot exists — that child is simply not rendered, with no warning; it is still in the DOM, just absent from the flattened tree. Conversely, a slot with nothing assigned renders its own children as fallback content, which disappears the moment anything is assigned.
code
html · 16 lines<script>
customElements.define('my-card', class extends HTMLElement {
connectedCallback() {
if (this.shadowRoot) return;
this.attachShadow({ mode: 'open' }).innerHTML = `
<header><slot name="title">Untitled</slot></header>
<section><slot></slot></section>`;
}
});
</script>
<my-card>
<h2 slot="title">Invoice</h2>
<p>Body text goes to the default slot.</p>
<p slot="foot">No matching slot, so this never renders.</p>
</my-card>go deeper
Know the two halves of the contract: slot="name" on the caller's child, name="name" on the <slot>, and a plain <slot> for everything unnamed. Say plainly that unmatched content does not render.
Explain that only direct children are slottable, that the first matching slot in tree order wins, and that a slot's own children are fallback shown only while nothing is assigned.
Be ready to debug the silent failure: content present in the DOM but absent from the render because assignedSlot is null. Mention the whitespace-text-node trap that defeats fallback content.
Own the API-design view: slot names are the public contract between component and caller, so the count and naming of slots is a compatibility surface you version, not an implementation detail.
## The two trees, and what slotting actually does A custom element with a shadow root has two sets of children: - its **light DOM** children — what the page author wrote between the tags; - its **shadow tree** — what the component itself created inside `attachShadow`. By default the light children are not rendered at all: the shadow tree replaces them. A `<slot>` is the component saying "render some of the caller's children *here*". The browser builds a third, virtual view — the **flattened tree** — in which each slot is replaced by the nodes assigned to it, and that flattened tree is what gets rendered. Nothing moves. Slotting is a projection, not a relocation. ## The matching rules 1. **Only direct children of the host are slottable.** Elements *and* text nodes. A grandchild can never be assigned; it renders as part of its slotted ancestor or not at all. 2. **`slot="name"` on the child, `name="name"` on the slot.** The match is an exact string comparison. 3. **No `slot` attribute means the default slot** — a `<slot>` element with no `name` (equivalently `name=""`). 4. **First match in tree order wins.** Two `<slot name="footer">` elements in one shadow root: the first one receives everything, the second stays empty and shows its fallback. 5. **Several children may share one slot.** They render in document order, not in the order they were assigned. 6. **No match means no render.** No error, no console warning. ```html <my-card> <h2 slot="title">Invoice</h2> <!-- -> <slot name="title"> --> <p>Body text</p> <!-- -> <slot> (default) --> <p slot="foot">Total</p> <!-- no <slot name="foot">: invisible --> </my-card> ``` ## Fallback content The children of a `<slot>` element are its fallback: rendered only while the slot has no assigned nodes. ```html <slot name="icon"><svg class="default-icon"></svg></slot> ``` Assign anything to that slot and the default `<svg>` stops rendering — it is not merged with, and not appended to, the caller's content. Fallback is all-or-nothing per slot, which is why components that want "caller content *plus* a default decoration" use two slots rather than one slot with fallback. A subtlety worth knowing: whitespace text nodes are slottable. Formatted markup like ```html <my-card> <h2 slot="title">Invoice</h2> </my-card> ``` assigns the newline-and-indentation text nodes around the `<h2>` to the default slot. The default slot is therefore *not* empty and its fallback content will not show. Components that depend on fallback usually guard with `:host` styling or check `slot.assignedNodes({ flatten: true })` for meaningful content rather than trusting emptiness. ## The silent-failure trap The most common bug in a slot-based API is a typo in the slot name. Because unmatched children are simply absent from the flattened tree, the symptom is missing UI with a perfectly healthy DOM: `document.querySelector('[slot="foot"]')` finds the element, DevTools shows it under the host, it is just not painted. Two habits contain this: always declare a default `<slot>` so unnamed and misrouted content has somewhere to go if that suits your design, and in development log any host child whose `assignedSlot` is `null`. ```js for (const child of host.children) { if (!child.assignedSlot) console.warn('unslotted:', child); } ``` `Element.assignedSlot` is the per-node view of the same relation that `slot.assignedNodes()` gives from the slot's side. ## Why the API is shaped this way Slots exist so a component can own **structure** while the caller owns **content**. The component decides that a card has a header region, a body region, and a footer region, and how they are laid out and labelled; the caller decides what goes in each. That is the same inversion of control as a `children` prop in a component framework, but resolved by the browser at render time rather than by a library at call time — which is why the component cannot inspect or transform what it is given, only decide where it lands.
- A component declares <slot name="icon"><svg/></slot> and the caller supplies an icon. Does the caller's icon render alongside the default svg?No. Fallback content renders only while the slot has no assigned nodes; the moment anything is assigned, the entire fallback stops rendering. It is a replacement, not a merge. If you want caller content plus a permanent decoration, render the decoration outside the slot or give it its own slot.
- Why can a component not slot a grandchild, for example the <li> elements inside a caller's <ul>?Only the host's direct children are slottable, so the `<ul>` is assignable but its `<li>` children are not. They render as part of the slotted `<ul>` subtree. If a component needs to place individual items, its API must ask the caller to write them as direct children of the host.
- The default slot's fallback content never appears, even when the caller writes no children. What is the likely cause?Whitespace. Formatted markup leaves newline-and-indentation text nodes as direct children of the host, and text nodes are slottable, so the default slot has assigned nodes and suppresses its fallback. Check with `slot.assignedNodes({ flatten: true })` and filter for non-whitespace, rather than assuming emptiness.
saying these in an interview costs you the question
- Thinks any descendant can carry a slot attribute
- Expects a console error when a slot name does not match
- Believes fallback content merges with assigned content
- Says slotted nodes are moved into the shadow tree
- Assumes duplicate slot names split the assigned nodes