For

Keyed list rendering — each item renders once per key, no re-renders on reorder.

For reconciles a reactive array into the DOM. Each item renders once per unique key — no full re-renders on reorder, add, or remove. The render callback runs only for new keys.

main.tsxrows.ts
import { For } from "elements-kit/for";
import { rows, add, removeLast, shuffle } from "./rows";

export class App {
  render() {
    return (
      <div style="padding: 1.5rem; font-family: system-ui, sans-serif; max-width: 640px;">
        <h2 style="margin-top: 0;">For — keyed list rendering</h2>
        <div style="display: flex; gap: 8px; margin-bottom: 1rem;">
          <button on:click={add}>add</button>
          <button on:click={removeLast}>remove last</button>
          <button on:click={shuffle}>shuffle</button>
        </div>
        <ul style="border: 1px solid #8884; border-radius: 8px; padding: 1rem; list-style: none; margin: 0;">
          <For each={rows} by={(row) => row.id}>
            {(row) => (
              <li style="padding: 4px 0;">
                #{row.id} — {row.label}
              </li>
            )}
          </For>
        </ul>
      </div>
    );
  }
}

API

import { For } from "elements-kit/for";

<ul>
  <For each={todos} by={(todo) => todo.id}>
    {(todo) => (
      <li style:text-decoration={computed(() => todo.done ? "line-through" : "none")}>
        <input
          type="checkbox"
          checked={computed(() => todo.done)}
          on:change={() => (todo.done = !todo.done)}
        />{" "}
        {todo.text}
      </li>
    )}
  </For>
</ul>
PropTypeDescription
eachT[] | (() => T[])Reactive array to render.
by(item: T, index: number) => string | numberKey function. Defaults to the index.
children(item: T, index: number) => ElementRender function. Called once per new key.

How reconciliation works

On each array change, For compares the new keys to the previous ones:

  • Existing key → reuses the rendered node. No teardown, no recreate.
  • New key → calls the render function once and mounts the result.
  • Missing key → unmounts the node.
  • Moved key → reorders the DOM in place.

Complexity is O(n + m) where n is the old array length and m is the new.

Per-item scope

Each item's render callback runs inside its own effectScope. onCleanup calls and nested effects registered during render belong to that scope and fire when the item's key leaves each — not only when the whole list unmounts. Use this to bind row-level resources (subscriptions, intervals, observers) to the row's real lifetime. See Scopes & cleanup.

Keys

Keys must be unique within each. Duplicate keys produce undefined reordering. If your data doesn't have a natural identifier, generate one at insertion time; don't rely on index as a key for mutable lists.

The render callback isn't reactive

For's render function runs once per new key — it's not an effect. If you want a row to react to changes in todo.done, read it through () => todo.done or computed(() => todo.done) inside the returned JSX. A bare {todo.done} captures a snapshot.

Non-reactive arrays

each accepts both a plain array and a signal / getter. A plain array renders once; passing a signal makes the list live:

// Static — rendered once
<For each={[1, 2, 3]} by={(n) => n}>{(n) => <li>{n}</li>}</For>

// Reactive — reruns on signal change
const items = signal<string[]>([]);
<For each={items} by={(x) => x}>{(x) => <li>{x}</li>}</For>

See also