Tutorial: Build Reusable Panels
Four steps build one settings page with three collapsible panels that share a header and toggle mechanism but show completely different content each. By the end, one small component definition drives all three, plus a fourth piece that renders data it doesn't own.
Step 1: One Component, Two Instances
Writing data-component="panel" a second time really does give you a second, fully independent instance. One wildflower.component('panel', {...}) registration; every element that names it gets its own state.
<div data-component="panel" data-title="Profile">...</div>
<div data-component="panel" data-title="Notifications">...</div>
<Panel/><Panel/> gives you. It just doesn't look like a stamp the way a repeated component tag does, because it's an attribute, not a tag. It behaves identically anyway.
Step 2: Different Content Per Instance
The panel's body was hardcoded text in step 1. Real panels need real content, different per instance. A data-slot-container in the component's own layout marks where content goes; a data-slot at each usage site supplies it. The panel never sees an input, a checkbox, or a button, just a hole labeled "body."
<div class="panel-body" data-show="expanded">
<div data-slot-container="body"></div>
</div>
<!-- at the usage site, a sibling of the component element -->
<div data-slot="body">
<label>Name <input id="name-input" type="text"></label>
</div>
Step 3: Multiple Named Slots
Slots aren't all-or-nothing. Add a second named container (a badge next to the title) and every instance decides independently whether to fill it. The component definition doesn't grow to accommodate the new slot; it just names it in its own layout.
<span class="panel-header-left">
<span data-bind="title"></span>
<span data-slot-container="badge"></span>
</span>
<!-- Notifications fills it: -->
<div data-slot="badge"><span class="badge">3</span></div>
<!-- Profile and Danger Zone don't. Nothing to opt out of. -->
data-slot-container is simply empty. There's no error, no warning, no default to override.
Step 4: When the Child Owns the Data
Slots project static content the parent already has fully formed. Sometimes it's the other way around. The child owns a list of data it computes or fetches itself, and the parent wants to control how each item looks, without the child knowing anything about icons, classes, or layout. That's Configurable Templates. A parent declares a named <template data-item-template="...">; any descendant can render its own list through it with data-use-template.
<!-- settings-page (parent) declares the template, anywhere in its
own markup, not necessarily right next to where it's used -->
<template data-item-template="logEntry">
<div class="log-entry" data-bind-class="type === 'warning' ? 'warn' : ''">
<span data-bind="icon"></span>
<span data-bind="message"></span>
</div>
</template>
<!-- activity-feed (child) renders ITS OWN data through it -->
<div data-component="activity-feed">
<div data-list="entries">
<template data-use-template="logEntry"></template>
</div>
</div>
data-bind-class="entryClass") resolves against whichever component actually renders the list (activity-feed here), not the component that defined the template. An inline expression like type === 'warning' ? 'warn' : '' evaluates directly against the item's own fields, so it works no matter which component's markup the template lives in. That's what lets settings-page decide what "warning" looks like without activity-feed ever needing to expose a class name.
activity-feed's three log entries are hardcoded here for the example, but they could just as easily come from a store or a data-query. It decides what happened, and settings-page decides how a warning-type entry looks different from an info-type one. Neither component could do the other's job without the split.
What You Built
One component definition ended up driving three independent, differently-configured instances. Two named slots, one always filled and one optional, controlled what each panel showed. A fourth component rendered its own data through a template it never had to know existed, with no duplicated markup and no parallel state fields typed out three times.
Where to go deeper:
- Composition Patterns: the conceptual overview this tutorial skipped past
- Slots: fallback content, reactive slot content, multiple slots at once
- Props: passing plain data down instead of markup
- Configurable Templates: reactive updates, actions inside templates, list binding in depth
- Advanced Configurable Templates: template hierarchy lookup, conditional templates, multiple templates in one component
- Single-File Components: organizing a component's HTML, CSS, and JS together