Skip to content

Comments

Support multiple root elements in x-for templates#4750

Open
calebporzio wants to merge 2 commits intomainfrom
x-for-multi-element
Open

Support multiple root elements in x-for templates#4750
calebporzio wants to merge 2 commits intomainfrom
x-for-multi-element

Conversation

@calebporzio
Copy link
Collaborator

@calebporzio calebporzio commented Feb 18, 2026

Situation

x-for renders one element per iteration. Works great — until you need siblings:

<tr>
    <template x-for="(color, idx) in colors">
        <td x-text="color"></td>
        <td x-text="idx"></td>
    </template>
</tr>

What happens:

  1. You put two <td>s in the template
  2. Only the first <td> renders per iteration — the second is silently dropped
  3. You try wrapping in a <div> — browser strips it (invalid inside <tr>)
  4. No warning. No error. Just missing elements.

Known pain point since 2020 (#450, discussion #935).

Problem

x-for's clone logic hardcodes single-element extraction:

let clone = document.importNode(templateEl.content, true).firstElementChild
  • .firstElementChild grabs one element, discards the rest
  • The lookup map, reordering, cleanup, and scope all assume one element per iteration
  • No warning when children are dropped

Solutions considered

# Approach Verdict
1 Docs-only (original PR #4730) ❌ Doesn't fix it. Several workarounds empirically broken.
2 Always use comment markers ❌ Unnecessary overhead for single-element templates.
3 Auto-wrap in table elements ❌ No valid HTML wrapper for <td> siblings inside <tr>.
4 New directive (x-for-each) ❌ Two directives for one concept. Confusing.
5 Console warning ⚡ Shipped separately in #4752. Visible failure, not a fix.
7 Fragment class abstraction ❌ Overengineered. Comment markers are simpler.
11 Auto-detect fragment mode Chosen

Chosen approach: auto-detect fragment mode

When templateEl.content.children.length > 1 → switch to fragment mode with comment-node boundaries.
When === 1 → existing code, unchanged, zero overhead.

Why:

  • 🟢 Vue 3 (fragment VNodes) and Svelte (comment anchors) both solved it this way
  • 🟢 Alpine's morph plugin already uses this pattern (Block class with startComment/endComment)
  • 🟢 _x_lookup and _x_refreshXForScope are fully internal — no public API changes
  • 🟢 Comment nodes invisible to CSS, don't affect :nth-child, survive inside <tr>

Based on the problem identified in #4730 by @NightFurySL2001.

🤖 Generated with Claude Code

x-for previously used .firstElementChild to clone exactly one element
per iteration, silently discarding any siblings. This made it impossible
to render multiple <td> elements per iteration inside a table row —
a common need with no clean workaround.

When the template has multiple children, x-for now clones the full
fragment and uses comment-node boundary markers to track each iteration
group. Single-element templates are unchanged (no markers, no overhead).

Co-Authored-By: NFSL2001 <NightFurySL2001@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

### Limitations with `<table>` elements

HTML tables have strict parsing rules — a `<div>` is not valid inside `<tr>`, so the browser will strip it before Alpine runs. If you need to iterate table cells, either place multiple `<td>` elements directly in the template (as shown above) or use `<tr>` as the root element:
Copy link

@NightFurySL2001 NightFurySL2001 Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be "td is not valid inside div" ? And should be "strict template parsing rule", not "strict table parsing rule"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsure? it might go both ways

…nt x-for

Verifies:
- Morph preserves Alpine state with multi-element x-for templates
- x-if as a direct child of multi-element x-for toggles correctly
- Event listeners bind to correct iteration scope across siblings
- 200-item list with 2 elements per iteration renders and reorders

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants