Slots
Slots let a parent inject HTML content into designated areas of a child component. The child defines placeholders with data-slot-container, and the parent fills them with data-slot.
Basic Slots
A component defines slot containers in its layout markup. The parent provides content for each named slot. During initialization, the framework moves each data-slot element into the matching data-slot-container:
<div data-component="status-card">
<!-- Component layout with slot containers -->
<div class="card">
<div class="card-header"
data-slot-container="header"></div>
<div class="card-body"
data-slot-container="body"></div>
</div>
<!-- Parent-provided slot content -->
<div data-slot="header">
<h5 class="mb-0">System Status</h5>
</div>
<div data-slot="body">
<p>Server: <strong data-bind="serverStatus"></strong></p>
<p>Uptime: <span data-bind="uptime"></span></p>
<button class="btn btn-primary btn-sm"
data-action="refreshStatus">
Refresh
</button>
<span class="ms-2 small text-muted"
data-bind="lastRefresh"></span>
</div>
</div>
wildflower.component('status-card', {
state: {
serverStatus: 'Online',
uptime: '14 days, 3 hours',
lastRefresh: ''
},
refreshStatus() {
// Simulate a status check
const statuses = ['Online', 'Degraded', 'Online', 'Online']
const uptimes = [
'14 days, 3 hours',
'2 days, 11 hours',
'30 days, 7 hours',
'7 days, 22 hours'
]
const i = Math.floor(Math.random() * statuses.length)
this.serverStatus = statuses[i]
this.uptime = uptimes[i]
this.lastRefresh =
'Refreshed at ' + new Date().toLocaleTimeString()
}
})
How Slots Work
During component initialization, the framework finds every data-slot-container element and looks for a matching data-slot with the same name. The slot content is moved into the container, replacing any default content.
| Attribute | Placed On | Purpose |
|---|---|---|
data-slot-container="name" |
Child layout markup | Placeholder where parent content will be inserted |
data-slot="name" |
Parent-provided content | Content to inject into the matching container |
Multiple Named Slots
Components can define as many named slots as needed. This is useful for layout components like cards, dialogs, or page shells. Slot content is fully reactive: data-bind, data-action, and other attributes work inside slots:
<div data-component="confirm-dialog">
<!-- Dialog layout with three slot containers -->
<div class="card">
<div class="card-header"
data-slot-container="header"></div>
<div class="card-body"
data-slot-container="body"></div>
<div class="card-footer d-flex justify-content-end gap-2"
data-slot-container="actions"></div>
</div>
<!-- Slot content injected by the parent -->
<div data-slot="header">
<h5 class="mb-0">Confirm Deletion</h5>
</div>
<div data-slot="body">
<p>Delete <strong data-bind="itemName"></strong>?</p>
<p class="text-muted small mb-0">This cannot be undone.</p>
</div>
<div data-slot="actions">
<button class="btn btn-secondary btn-sm"
data-action="cancel">Cancel</button>
<button class="btn btn-danger btn-sm"
data-action="confirmDelete">Delete</button>
</div>
<!-- Result display -->
<div class="mt-3 p-2 text-center" data-show="result">
<span class="badge bg-info" data-bind="result"></span>
</div>
</div>
wildflower.component('confirm-dialog', {
state: {
itemName: 'Annual Report.pdf',
result: ''
},
nextFile() {
const files = [
'Budget.xlsx', 'Presentation.pptx',
'Photo.jpg', 'Notes.txt'
]
this.itemName =
files[Math.floor(Math.random() * files.length)]
},
cancel() {
this.result = `Kept "${this.itemName}"`
this.nextFile()
},
confirmDelete() {
this.result = `Deleted "${this.itemName}"`
this.nextFile()
}
})
Slots vs Props vs Configurable Templates
| Pattern | Passes | Use When |
|---|---|---|
| Slots | HTML content | Parent provides markup for layout areas (headers, footers, actions) |
| Props | Data values | Parent passes typed, validated data that the child renders its own way |
| Configurable Templates | Templates + child data | Parent defines how to render, child provides the data context (like Vue scoped slots) |