Default Slot
The default slot is the simplest form. The child uses <t t-slot="default"/> as the injection point. The parent writes content between the child's opening and closing tags.
// ── Child: defines where slot content goes ─────────────────────────
class Card extends Component {
static template = xml`
<div class="card">
<div class="card-body">
<!-- Parent content is injected here -->
<t t-slot="default"/>
</div>
</div>
`;
}
// ── Parent: provides slot content ──────────────────────────────────
class App extends Component {
static components = { Card };
static template = xml`
<div>
<Card>
<!-- Everything between <Card>...</Card> goes into the default slot -->
<h2>Welcome!</h2>
<p>This is the card content.</p>
<button>Click me</button>
</Card>
</div>
`;
}
Named Slots
Named slots allow multiple injection points in the same component. The child defines multiple t-slot="slotName" points; the parent fills each using t-set-slot="slotName".
// ── Child: Modal with header, body, footer slots ───────────────────
class Modal extends Component {
static template = xml`
<div class="modal-overlay" t-on-click.self="() => props.onClose()">
<div class="modal" role="dialog" t-att-aria-label="props.title">
<div class="modal-header">
<!-- Named slot: header -->
<t t-slot="header">
<!-- Fallback content if no header slot provided -->
<h2 t-esc="props.title"/>
</t>
<button class="modal-close" t-on-click="() => props.onClose()">×</button>
</div>
<div class="modal-body">
<!-- Default slot: body content -->
<t t-slot="default"/>
</div>
<div class="modal-footer">
<!-- Named slot: footer -->
<t t-slot="footer">
<!-- Fallback: simple close button -->
<button t-on-click="() => props.onClose()">Close</button>
</t>
</div>
</div>
</div>
`;
static props = {
title: { type: String, optional: true },
onClose: { type: Function },
};
}
// ── Parent: fills the named slots ──────────────────────────────────
class ConfirmDialog extends Component {
static components = { Modal };
static template = xml`
<Modal onClose.bind="close">
<!-- Fill the header slot -->
<t t-set-slot="header">
<h2 class="text-danger">⚠️ Confirm Delete</h2>
</t>
<!-- Default slot (body) -->
<p>Are you sure you want to delete <strong t-esc="props.itemName"/>?</p>
<p class="text-muted">This action cannot be undone.</p>
<!-- Fill the footer slot -->
<t t-set-slot="footer">
<button class="btn-secondary" t-on-click="close">Cancel</button>
<button class="btn-danger" t-on-click="confirm">Delete</button>
</t>
</Modal>
`;
}
Slot Fallback Content
Content placed inside <t t-slot="name">...fallback...</t> in the child renders only when the parent does not provide content for that slot.
<!-- In the child template: -->
<div class="panel">
<div class="panel-header">
<t t-slot="title">
<!-- Shown ONLY when parent does not provide a "title" slot -->
<span>Untitled Panel</span>
</t>
</div>
<div class="panel-content">
<t t-slot="default">
<p class="empty-state">No content provided.</p>
</t>
</div>
</div>
Scoped Slots – Data from Child to Parent
Scoped slots let the child pass data back to the parent's slot content. The child provides variables when rendering the slot; the parent can use those variables inside its slot template.
// ── Child: VirtualList — passes each item + index to slot ──────────
class VirtualList extends Component {
static template = xml`
<div class="virtual-list">
<t t-foreach="props.items" t-as="item" t-key="item.id">
<!-- Slot with scope: passes item and item_index to parent -->
<t t-slot="item" item="item" index="item_index"/>
</t>
</div>
`;
static props = {
items: { type: Array },
};
}
// ── Parent: uses scoped slot variables ─────────────────────────────
class ProductPage extends Component {
static components = { VirtualList };
static template = xml`
<VirtualList items="state.products">
<t t-set-slot="item" t-slot-scope="slotProps">
<!-- slotProps.item and slotProps.index are available here -->
<div class="product-row">
<span t-esc="slotProps.index + 1"/>.
<strong t-esc="slotProps.item.name"/>
<span>$<t t-esc="slotProps.item.price"/></span>
</div>
</t>
</VirtualList>
`;
}
Real-World Layout Patterns
class PageLayout extends Component {
static template = xml`
<div class="page">
<header class="page-header">
<t t-slot="header">
<h1>Page</h1>
</t>
</header>
<div class="page-body">
<aside t-if="hasLeftPanel" class="page-sidebar">
<t t-slot="sidebar"/>
</aside>
<main class="page-main">
<t t-slot="default"/>
</main>
</div>
<footer class="page-footer">
<t t-slot="footer">
<p>© 2026 ylearner</p>
</t>
</footer>
</div>
`;
// Check if parent provided a sidebar slot
get hasLeftPanel() {
return !!this.slots.sidebar;
}
}
Slots: Letting the Parent Inject Content
Props pass data down. Slots pass markup down — they let a parent drop arbitrary template content into a hole the child defines. This is how you build reusable wrappers like cards, modals, and layouts that don't know their contents ahead of time.
// Child: Card defines a slot placeholder
class Card extends Component {
static template = xml`
<div class="card">
<t t-slot="default"/> <!-- parent content lands here -->
</div>`;
}
// Parent: everything between the tags fills the default slot
xml`<Card><h2>Title</h2><p>Body text</p></Card>`;
Named slots for multiple holes
// Child template
xml`<div class="modal">
<header><t t-slot="title"/></header>
<main><t t-slot="default"/></main>
</div>`;
// Parent fills each by name
xml`<Modal>
<t t-set-slot="title">Confirm</t>
Are you sure?
</Modal>`;
Why it matters: without slots, a reusable Card would need a prop for every possible piece of content. Slots invert that — the child owns the structure (borders, spacing), the parent owns the content. Clean separation, infinite reuse.
🏋️ Practical Exercise
Build a Tabs component using named slots:
Tabsreceives atabsarray prop of{ id, label }and renders tab buttons. State:activeId.- For each tab, render a named slot
t-slot="[tab.id]"— only show the active tab's slot. - In a parent, provide content for each tab:
<t t-set-slot="tab1">...</t>. - Add a scoped slot variant: each tab slot receives an
isActiveprop from the child so parent content can style itself differently when active.
🔥 Challenge Exercise
Build a reusable Card component that accepts content via a default slot and a named header slot, with fallback content. Explain what slots are (content projection) and how named and scoped slots work.
📋 Summary
- Default slot: child writes
<t t-slot="default"/>; parent provides content between component tags. - Named slots: child uses
<t t-slot="name"/>; parent fills with<t t-set-slot="name">...</t>. - Fallback content: body of
<t t-slot="name">fallback</t>renders when parent provides no content for that slot. - Scoped slots: child passes props into the slot with
<t t-slot="name" key="value"/>; parent accesses them viat-slot-scope="scope"thenscope.key. - Check if a slot was provided with
this.slots.slotName(truthy when content was given).
Interview Questions
- What are slots in OWL?
- How do you define and use a slot?
- What are named slots?
- What is a scoped slot?
- How do slots enable reusable layout components?
Related Topics
FAQ
Yes — slot content is evaluated in the parent's scope, not the child's. The template code you write between the child's tags has access to the parent's state, props, and methods. This is the key feature of slots: the child provides structure (CSS, layout), while the parent provides content with full access to its own data.
Props carry data (strings, numbers, objects, functions). Slots carry rendered template fragments — they can include other components, event listeners, reactive expressions, and full OWL template syntax. Passing HTML as a string prop would require t-raw (XSS risk) and loses all reactivity. Slots are the right tool for injecting structured UI.

