x-for and keys
The x-for directive repeats a piece of markup for each item of an array. This is how lists are rendered: tasks, products, comments. When the array changes, Alpine adds and removes elements on the page by itself.
A simple list
Like x-if, x-for goes on a <template>. The value is written as item in array:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<ul x-data="{ tasks: ['Buy milk', 'Call Oleg', 'Pay for internet'] }">
<template x-for="task in tasks">
<li x-text="task"></li>
</template>
</ul>The page shows a list of three items. The task variable takes each value of the array in turn and is visible inside the <template>.
The index
To get an item's position, list two variables in parentheses:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<ul x-data="{ tasks: ['Buy milk', 'Call Oleg'] }">
<template x-for="(task, index) in tasks">
<li x-text="(index + 1) + '. ' + task"></li>
</template>
</ul>The items are numbered: "1. Buy milk", "2. Call Oleg".
Keys
If list items can be removed or reordered, each one needs a key — a unique value by which Alpine recognizes the item. The key is set with the :key attribute on the <template>. Usually it is the object's id:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<ul x-data="{
tasks: [
{ id: 1, title: 'Buy milk' },
{ id: 2, title: 'Call Oleg' },
{ id: 3, title: 'Pay for internet' }
],
remove(id) {
this.tasks = this.tasks.filter(task => task.id !== id);
}
}">
<template x-for="task in tasks" :key="task.id">
<li>
<span x-text="task.title"></span>
<button @click="remove(task.id)">Delete</button>
</li>
</template>
</ul>The "Delete" button removes its own task: remove() keeps every task except the chosen one, and Alpine removes the matching item. :key is short for x-bind:key; x-bind is the next lesson.
A range of numbers
Instead of an array you can give a number — the variable then runs from 1 up to it:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data>
<template x-for="page in 5">
<button x-text="page"></button>
</template>
</div>Five buttons numbered 1 to 5 appear — a starting point for pagination.
Common mistake
Using the index as the key: :key="index". After the first task is removed, the second one gets index 0, and Alpine treats it as the item that was deleted. Element state — typed text, a checked box — can end up on the wrong row. Use a stable id as the key, and keep one root element inside the <template>.