Skip to content

Commit 005259d

Browse files
calebporzioNightFurySL2001claude
committed
Warn when x-for template has multiple root elements and document table workaround
x-for silently discards all but the first child element of a template. This adds a console.warn so users know what's happening, and documents the table-specific workaround (use <tr> as the root element). Co-Authored-By: NFSL2001 <NightFurySL2001@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b357068 commit 005259d

File tree

3 files changed

+39
-0
lines changed

3 files changed

+39
-0
lines changed

packages/alpinejs/src/directives/x-for.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ function loop(templateEl, iteratorNames, evaluateItems, evaluateKey) {
111111
return
112112
}
113113

114+
if (templateEl.content.children.length > 1)
115+
warn('x-for templates require a single root element, additional elements will be ignored.', templateEl)
116+
114117
let clone = document.importNode(templateEl.content, true).firstElementChild
115118
let reactiveScope = reactive(scope)
116119
addScopeToNode(clone, reactiveScope, templateEl)

packages/docs/src/en/directives/for.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,18 @@ but this code will work:
133133
</p>
134134
</template>
135135
```
136+
137+
### Limitations with `<table>` elements
138+
139+
HTML tables have strict parsing rules — elements like `<td>` and `<th>` can only appear inside `<tr>`, and `<div>` is invalid inside table rows. If you wrap table cells in a `<div>` inside your `x-for` template, the browser's HTML parser will strip them before Alpine runs. The workaround is to make `<tr>` the root element of your template, iterating over rows instead of cells:
140+
141+
```alpine
142+
<table x-data="{ colors: ['Red', 'Orange', 'Yellow'] }">
143+
<template x-for="(color, index) in colors">
144+
<tr>
145+
<td x-text="index"></td>
146+
<td x-text="color"></td>
147+
</tr>
148+
</template>
149+
</table>
150+
```

tests/cypress/integration/directives/x-for.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,27 @@ test('x-for eagerly cleans tree',
671671
}
672672
)
673673

674+
test('x-for works with tr as root element inside table',
675+
html`
676+
<table x-data="{ colors: ['Red', 'Green', 'Blue'] }">
677+
<template x-for="(color, index) in colors">
678+
<tr>
679+
<td x-text="color"></td>
680+
<td x-text="index"></td>
681+
</tr>
682+
</template>
683+
</table>
684+
`,
685+
({ get }) => {
686+
get('tr:nth-of-type(1) td:nth-of-type(1)').should(haveText('Red'))
687+
get('tr:nth-of-type(1) td:nth-of-type(2)').should(haveText('0'))
688+
get('tr:nth-of-type(2) td:nth-of-type(1)').should(haveText('Green'))
689+
get('tr:nth-of-type(2) td:nth-of-type(2)').should(haveText('1'))
690+
get('tr:nth-of-type(3) td:nth-of-type(1)').should(haveText('Blue'))
691+
get('tr:nth-of-type(3) td:nth-of-type(2)').should(haveText('2'))
692+
}
693+
)
694+
674695
// To support rerendering alongside x-sort
675696
test('x-for handles moved elements correctly',
676697
html`

0 commit comments

Comments
 (0)