Language
Loops
[for-each]: lists, the index i, objects, data from files, nested loops, and live loops that follow a signal.
[for-each]
[for-each] writes its body once for each item of a list. Try it: add a name and press Run:
${ const names = ["Adam", "Eid", "Ismail"]; }$
[ul]
[for-each = ${ names }$, as: "name"]
[li]${ name }$[end]
[end]
[end]
- The list comes from
${ }$and is read when the page is built. asnames the item. Without it, the item is calledvalue.- For a list that changes in the browser, use a live loop.
The index: i
i is the item's position, starting at 0:
[for-each = ${ ["Gold", "Silver", "Bronze"] }$]
[p = data-place: ${ i }$]${ i + 1 }$. ${ value }$[end]
[end]<p data-place="0">1. Gold</p>
<p data-place="1">2. Silver</p>
<p data-place="2">3. Bronze</p>
Lists of objects
Each item can be an object. Use its fields in text and in props:
${ const users = [{ name: "Adam", role: "admin" }, { name: "Elmi", role: "editor" }]; }$
[for-each = ${ users }$, as: "user"]
[p = class: ${ user.role }$]${ user.name }$[end]
[end]<p class="admin">Adam</p>
<p class="editor">Elmi</p>
Data from a file
Import a JSON file in ${ }$ and loop over it:
[
{ "title": "Hello, ArcMoon", "date": "2026-10-01" },
{ "title": "Loops in pages", "date": "2026-10-13" }
]${ import posts from "./posts.json"; }$
[for-each = ${ posts }$, as: "post"]
[article]
[h2]${ post.title }$[end]
[time]${ post.date }$[end]
[end]
[end]<article>
<h2>Hello, ArcMoon</h2>
<time>2026-10-01</time>
</article>
<article>
<h2>Loops in pages</h2>
<time>2026-10-13</time>
</article>
Nested loops
Loops can go inside loops. The inner i hides the outer one, so keep the outer index in a const first:
${ const rows = [["a", "b"], ["c"]]; }$
[for-each = ${ rows }$, as: "row"]
${ const r = i; }$
[for-each = ${ row }$, as: "cell"]
[span]${ r }$.${ i }$ ${ cell }$[end]
[end]
[end]<span>0.0 a</span>
<span>0.1 b</span>
<span>1.0 c</span>
What you can loop over
- Arrays, strings,
SetandMap: anything JavaScript can loop over withfor…of. - An object: use
Object.entries(obj), thenentry[0]is the key andentry[1]the value. - A number of times:
Array.from({ length: 3 }, (_, n) => n + 1)gives1, 2, 3. - An empty list writes nothing. A plain object or a number writes nothing too, with a warning that says how to loop over it.
${ const prices = { tea: 2, cake: 4 }; }$
[for-each = ${ Object.entries(prices) }$, as: "entry"]
[p]${ entry[0] }$: ${ entry[1] }$ USD[end]
[end]<p>tea: 2 USD</p>
<p>cake: 4 USD</p>
Live loops
When the list is runtime ${ }$, the loop runs in the browser too: it keeps one row per item,
and the rows follow the list as it changes. Since ArcMoon 1.1:
runtime ${
import { signal } from "arcmoon/reactive";
const todos = signal([{ id: 1, title: "Read a book" }, { id: 2, title: "Call Eid" }]);
const remove = (id) => todos(todos().filter((t) => t.id !== id));
}$
[ul]
[for-each = runtime ${ todos() }$, as: "todo", key: "id"]
[li]
runtime ${ i + 1 }$. runtime ${ todo.title }$
[button = onclick: runtime ${ () => remove(todo.id) }$]Remove[end]
[end]
[end]
[end]<ul>
<!--arcm-each:e0--><!--arcm-row:1-->
<li>
<!--arcm:t1-->1<!--/arcm-->.
<!--arcm:t2-->Read a book<!--/arcm-->
<button data-arcm-in="e3">Remove</button>
</li>
<!--arcm-row:2-->
<li>
<!--arcm:t1-->2<!--/arcm-->.
<!--arcm:t2-->Call Eid<!--/arcm-->
<button data-arcm-in="e3">Remove</button>
</li>
<template data-arcm-ref="e0">
<li>
<!--arcm:t1--><!--/arcm-->.
<!--arcm:t2--><!--/arcm-->
<button data-arcm-in="e3">Remove</button>
</li>
</template>
</ul>
- The list is a live value, usually a signal. Set a new list to change it:
todos([...todos(), item]). todoandiexist only inruntime ${ }$inside the loop.keynames a property that tells items apart. Rows stay with their items: removing or reordering items moves the real rows, so an input keeps its focus. Withoutkey, rows follow positions.- Rows update every time the list is set, even when an item was changed in place. Events always use the row's current item.
The first rows are in the HTML
When the list starts as a literal (signal([…])) or an exported value, ArcMoon writes the first rows into the HTML.
The list shows before the script runs, and search engines see it. In those rows, a value is filled in when it only needs the item,
i, exported values and plain JavaScript (Math, String, JSON…).
Other values, like another signal or your own function, fill in when the script runs. Events are always added by the script.
Not inside a live loop (yet)
These stop the build with a clear message: components, a live loop inside another, [slot], [script], and arcm-ref (it would be on every row; use arcm-shared-ref and ArcMoon.refs()). Put the loop inside an element, like [ul] or [main].
✗ "todo" only exists in the browser, inside a live [for-each]: use runtime ${ todo }$, not ${ todo }$Mistakes
With as, value doesn't exist. Use the name you chose:
✗ ReferenceError: value is not defined