Lists, transitions, and the DOM

Choose x-show or x-if

Decide whether hidden DOM should remain mounted or be created and destroyed.

Alpine gives you two ways to hide something. x-show hides the element with CSS and keeps it in the DOM. x-if removes the element entirely and recreates it when the condition turns true again.

Same visual result. Very different behavior underneath.

x-show keeps everything

The filter panel opens and closes many times per session. Use x-show:

<div x-data="{ open: false }">
  <button @click="open = !open">Filters</button>
  <form x-show="open">
    <input name="query" x-model="query">
  </form>
</div>

Close it, open it again, and the search text is still in the input. The element never left. Nothing inside was reinitialized. Toggling is just display: none flipping on and off, so it’s fast.

x-if starts fresh every time

x-if goes on a <template> with one child, like x-for:

<div x-data="{ editing: false }">
  <button @click="editing = true">Edit</button>
  <template x-if="editing">
    <form x-data="{ title: '' }">
      <input name="title" x-model="title">
    </form>
  </template>
</div>

Set editing to false and the form is gone from the DOM. Set it to true and Alpine builds it again from the template. The inner x-data runs again, so title is empty. Any init() runs again. A destroy() method, if you defined one, ran when it was removed.

How I pick

I ask three questions:

  • Does the hidden thing hold state the user expects back? Typed text, scroll position, an open sub-panel. x-show.
  • Is it expensive or rare? A modal with a third-party widget that’s opened once a week. x-if, so it costs nothing until needed.
  • Should it reset every time? A “new issue” form that must start blank. x-if gives you that for free.

For the board: x-show for the filter panel and the row disclosures. x-if for the editor, because each edit should start from the current issue, not from whatever was typed last time.

The swap that bites

The mistake is treating them as interchangeable. Someone switches x-show to x-if to “clean up the DOM” and three things break at once:

  • focus was on an element that no longer exists, so it falls back to <body>
  • a $refs.title lookup returns undefined because the ref was destroyed
  • a date picker library initialized on the input is gone, and its cleanup never ran

Going the other way has a cost too. A form wrapped in x-show still submits its fields with the page, hidden or not.

Build the editor both ways in your board. Open it, type, close it, open it again. Write down what came back and what didn’t. That list is the decision.

Lesson completed