Lists
The $each struct maps iterable values to independently managed DOM fragments.
Rendering an iterable
Use $each with a list and a render callback. Prefer .$withKey whenever items have a stable identifier.
import { grain } from '@grainular/grains';
import { $each, html } from '@grainular/nord';
const tasks = grain([
{ id: 'write', title: 'Write the guide' },
{ id: 'publish', title: 'Publish it' },
]);
const TaskList = () => html`
<ul>
${$each(tasks)
.$withKey((task) => task.id)
.$as((task) => html`<li>${task.title}</li>`)}
</ul>
`;Reactive collections
When the source is subscribable, $each reconciles it whenever it changes. Set a new array when updating a grain so subscribers receive the change.
tasks.update((current) => [...current, { id: 'review', title: 'Review the guide' }]);Existing keyed items keep their DOM and lifecycle while Nørd inserts, removes, or moves only what changed.
Item identity
Keys describe an item's identity, not its position. They must be unique among the current items and remain the same when an item moves.
.$as without .$withKey uses the item value itself as the key. That is fine for unique, stable object references, but a key function is safer for primitives, duplicate values, and objects recreated during updates.
The second render argument is a subscribable index. Use it directly when an item's displayed position should update after reordering.
$each(tasks)
.$withKey((task) => task.id)
.$as((task, index) => html`<li>${index}. ${task.title}</li>`);Adding and removing items
Adding an item renders one new fragment. Removing an item disconnects its nodes and runs cleanup registered by its directives and child components. Reordering keyed items moves the existing nodes instead of recreating them.
Empty collections
$each renders nothing for an empty array by default. Chain .$empty after
.$as when the list owns its empty-state markup. The fallback is removed when
items are added and restored when they are all removed.
const Tasks = () => html`
<ul>
${$each(tasks)
.$withKey((task) => task.id)
.$as((task) => html`<li>${task.title}</li>`)
.$empty(() => html`<li>No tasks yet.</li>`)}
</ul>
`;