Do You Need data-list?
data-list is the powerful option for rendering a collection, not the only one. It buys two things: per-item reactivity (change one row's field and only that cell repaints) and keyed diffing (add, remove, or reorder without re-rendering the rest). Many lists need neither. When yours does not, a lighter pattern is simpler to reason about, faster, and works in every build, including Nano, which ships no list renderer at all.
data-list for the case it was built for: a frequently changing list whose individual cells update live.
Every list uses a <template>
The markup is nearly identical no matter which approach you pick. A data-list block and a hand-rendered list hold the same HTML <template>; the difference is who stamps it. With data-list, the framework stamps and diffs it for you. Without it, you clone the template yourself with one or two lines of plain DOM.
Pattern 1: Static data (stamp once)
For data that is fixed when the page renders, such as a navigation menu, a footer, or a snapshot table, clone a template per item and set its text. The items are not components and carry no reactivity, so the framework never touches them. This is the cheapest list there is.
<ul id="menu"></ul>
<template id="menu-item">
<li><a></a></li>
</template>
const tpl = document.getElementById('menu-item');
const menu = document.getElementById('menu');
for (const link of links) {
const li = tpl.content.firstElementChild.cloneNode(true);
const a = li.querySelector('a');
a.textContent = link.label;
a.href = link.href;
menu.appendChild(li);
}
Pattern 2: A filtered or sorted view (re-render on change)
When a search, filter, or sort changes which items are visible, you are replacing the whole visible set anyway, so keyed diffing gains you nothing. Keep the data in state, derive the visible slice with a computed, and re-stamp the list body when it changes. The parts that are cheap to keep reactive, like the query and the result count, stay declarative with data-bind.
<input data-action="input:filter" placeholder="Search">
<p><span data-bind="count"></span> results</p>
<div id="results"></div>
<template id="row">
<div class="row"><span class="name"></span></div>
</template>
wildflower.component('finder', {
state: { query: '' },
computed: {
results() {
const q = this.query.toLowerCase();
return DATA.filter(d => d.name.toLowerCase().includes(q));
},
count() { return this.results.length; }
},
init() { this.render(); },
filter(event) { this.query = event.target.value; this.render(); },
render() {
const list = this.element.querySelector('#results');
list.textContent = ''; // clear
const tpl = document.getElementById('row');
for (const item of this.results) { // re-stamp the visible set
const el = tpl.content.firstElementChild.cloneNode(true);
el.querySelector('.name').textContent = item.name;
list.appendChild(el);
}
}
});
The example above re-renders from the filter action. You can also trigger it declaratively with a watch, so the list refreshes whenever the query changes, however it was set:
watch: {
query() { this.render(); } // re-render on any change to `query`
}
Live example: the Searchable List demo filters an array of HTTP status codes this way, in the Nano build.
Variant: filter a fixed set in place
When the full set is fixed and you are only narrowing it, such as a reference table or a docs index, you do not need to re-stamp at all. Render every item once as static HTML, tag each with what it belongs to, and toggle its display as the filter changes. The content stays in the page, so it is indexable and readable with JavaScript disabled, and filtering is one loop over the elements you already have.
<input data-action="input:filter" placeholder="Search">
<div class="entry" data-categories="core">WF-101 …</div>
<div class="entry" data-categories="state bindings">WF-EFFECT …</div>
wildflower.component('reference', {
state: { query: '' },
watch: { query() { this.apply(); } },
filter(event) { this.query = event.target.value; },
apply() {
const q = this.query.trim().toLowerCase();
for (const el of this.element.querySelectorAll('.entry')) {
el.style.display = el.textContent.toLowerCase().includes(q) ? '' : 'none';
}
}
});
Live example: the Error Codes reference in these docs filters about fifty static entries exactly this way, with a watch and one show/hide loop.
Pattern 3: Interactive rows (each row a component)
When rows have their own behavior, such as a toggle, an input, or a live value, make each row its own component. You get per-item reactivity from each row's own state, while list membership stays under your control. Appending a data-component element initializes it, and removing it tears it down: the same mutation observer that powers dynamic component detection handles both, so there is no scan() or destroy call to remember.
<ul id="todos"></ul>
<template id="todo-row">
<li data-component="todo-item">
<span data-bind="text"></span>
<button data-action="remove">Done</button>
</li>
</template>
// add a row
addTodo(text) {
const el = document.getElementById('todo-row')
.content.firstElementChild.cloneNode(true);
el.dataset.text = text;
document.getElementById('todos').appendChild(el); // initialized automatically
}
// inside the todo-item component
wildflower.component('todo-item', {
computed: { text() { return this.element.dataset.text; } },
remove() { this.element.remove(); } // destroyed automatically
});
Live example: the World Clock demo renders its add/remove city wall this way, each clock a self-ticking component.
When data-list earns its place
Reach for data-list when the list changes often and individual cells update in place, so re-rendering the whole body each time would be wasteful and would lose input focus or scroll position. A live-editing table, a reactive to-do list with inline editing, or any large collection that adds, removes, and reorders while cells update is exactly what its keyed diffing was built for. That capability is what separates the Mini build and above from Nano.
| Your list is… | Use | Cost |
|---|---|---|
| Fixed at render time | Stamp a template once (Pattern 1) | Almost nothing; works in Nano |
| A filtered / sorted / searched view | Re-render on change (Pattern 2) | One render method; works in Nano |
| Made of interactive rows | Row as a component (Pattern 3) | A per-row component; works in Nano |
| Frequently mutated with live per-cell updates | data-list | The list renderer (Mini build and above) |
data-list, and one of the three patterns above will be lighter and easier to follow.