Skip to content
Alpine.js Conditionals & Loops — Step-by-Step Guide

Alpine.js Conditionals & Loops — Step-by-Step Guide

DodaTech Updated Jun 6, 2026 11 min read

Alpine.js conditionals and loops let you control what appears on the page — show content only when a condition is true, hide it when false, and display lists of data with just a few lines of HTML.

What You’ll Learn

By the end of this tutorial, you’ll understand how to conditionally show/hide elements with x-show and x-if, loop over arrays and objects with x-for, animate transitions with x-transition, and combine all three to build dynamic, data-driven interfaces.

Before starting: Make sure you’re comfortable with Getting Started and Bindings & Events — especially x-data and how Alpine components manage state.

Why Conditionals & Loops Matter

Think about any interactive website you use. A dropdown menu appears when you click a button. A list of products filters as you type a search. A modal pops up when you click “Sign In.” All of these are examples of conditional rendering — showing or hiding content based on state.

Now imagine displaying a product catalog. Without loops, you’d write HTML for every single product individually. With loops, you write the HTML once and tell Alpine to repeat it for each item in your data.

Real-world use: The Doda Browser downloads page uses Alpine to show/hide download options based on your operating system. If you’re on Windows, it shows the Windows installer. On macOS, the .dmg file. The logic is handled by x-show and x-if — clean, simple, and instant.

Where This Fits in Your Learning Path

    flowchart LR
    A["Getting Started"] --> B["Bindings & Events"]
    B --> C["**Conditionals & Loops**"]
    C --> D["Transitions & Plugins"]
    D --> E["Real Alpine.js Apps"]
    style C fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#fff
  

Conditional Rendering with x-show

x-show is the simplest way to conditionally show content. It works by toggling the CSS display property — when the condition is false, Alpine adds display: none; to the element. When true, it removes it.

<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show="open">
    This content is always in the DOM, just hidden.
  </div>
</div>

What’s happening:

  1. Clicking the button toggles open between true and false.
  2. When open is true, Alpine removes display: none — the div appears.
  3. When open is false, Alpine adds display: none — the div disappears.

Key thing to understand: The element is always in the DOM. You can verify this by inspecting the page with browser DevTools. The div exists whether it’s visible or hidden. This matters because:

  • Good for frequently toggled elements — dropdowns, modals, tooltips. No DOM creation/destruction overhead.
  • Bad for expensive content — if the hidden content contains images, videos, or complex components, they still load and consume memory.

x-show.transition — Animated Toggling

Alpine makes it easy to add a fade transition to x-show:

<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show.transition="open">
    This fades in and out smoothly.
  </div>
</div>

The .transition modifier adds a default 150ms fade. We’ll cover custom transitions later in the Transitions & Plugins tutorial.


Conditional Rendering with x-if

x-if is more aggressive than x-show. Instead of hiding the element with CSS, it completely removes or adds the element from the DOM.

<div x-data="{ showDetails: false }">
  <button @click="showDetails = !showDetails">
    <span x-text="showDetails ? 'Hide' : 'Show'"></span> Details
  </button>
  <template x-if="showDetails">
    <div class="details-panel">
      <p>This content is COMPLETELY removed from the DOM when hidden.</p>
      <p>Any audio/video players will stop. Any observers disconnect.</p>
    </div>
  </template>
</div>

Critical rule: x-if must be used on a <template> tag. This is because Alpine needs a container that doesn’t render anything by itself — <template> is perfect because the browser ignores its content until Alpine processes it.

When to use x-if vs x-show:

ScenarioUseWhy
Dropdown menu that opens/closes frequentlyx-showFaster — just CSS toggle, no DOM work
Modal dialog that opens rarelyx-ifSaves memory — content exists only when shown
Tab panel with heavy content (images, charts)x-ifDon’t render what the user isn’t looking at
Tooltip on hoverx-showNeeds to appear/disappear instantly
Video player you want to pause when hiddenx-ifRemoving from DOM stops playback
Accordion section with text contentx-showText is cheap to keep in DOM

Memory rule of thumb: Content that’s expensive to create or needs to stop when hidden → use x-if. Content that’s cheap and toggles frequently → use x-show.


Looping with x-for

x-for iterates over arrays and objects, rendering a template for each item. Like x-if, it must be on a <template> tag.

Basic Array Loop

<div x-data="{ items: ['Apple', 'Banana', 'Cherry'] }">
  <ul>
    <template x-for="(item, index) in items" :key="index">
      <li>
        <span x-text="`${index + 1}. ${item}`"></span>
      </li>
    </template>
  </ul>
</div>

What each part means:

(item, index) in items:

  • item — the current element in the array (first 'Apple', then 'Banana', then 'Cherry')
  • index — the position number (0, 1, 2)
  • items — the array we’re looping over

<template x-for="..." :key="index">:

  • The <template> tag itself produces no output — it’s just a container for Alpine’s instructions.
  • :key="index" helps Alpine track which items have changed. Always provide a key.

Output:

1. Apple
2. Banana
3. Cherry

Iterating Objects

You can loop over object properties too:

<div x-data="{ user: { name: 'Alice', role: 'Admin', email: 'alice@example.com' } }">
  <dl>
    <template x-for="(value, key) in user" :key="key">
      <div>
        <dt x-text="key"></dt>
        <dd x-text="value"></dd>
      </div>
    </template>
  </dl>
</div>

Output:

name     Alice
role     Admin
email    alice@example.com

The :key Attribute

The :key attribute is how Alpine identifies which items in a list are new, removed, or moved. Think of it as giving each item a nametag that stays with it even when the list order changes.

<!-- Best: unique ID from your data -->
<template x-for="item in items" :key="item.id">
  <div x-text="item.name"></div>
</template>

<!-- OK: array index (but may cause issues with reordering) -->
<template x-for="(item, index) in items" :key="index">
  <div x-text="item"></div>
</template>

<!-- Wrong: no key at all -->
<template x-for="item in items">

Why keys matter: Without keys, if you add an item to the middle of a list, Alpine doesn’t know which items are new and which are old. It might re-render everything, causing flickering and lost state (like input focus or scroll position).

Nesting x-for Loops

You can nest loops for complex hierarchical data:

<div x-data="{
  categories: [
    { name: 'Fruits', items: ['Apple', 'Banana'] },
    { name: 'Vegetables', items: ['Carrot', 'Broccoli'] }
  ]
}">
  <template x-for="cat in categories" :key="cat.name">
    <div>
      <h3 x-text="cat.name"></h3>
      <ul>
        <template x-for="(item, i) in cat.items" :key="i">
          <li x-text="item"></li>
        </template>
      </ul>
    </div>
  </template>
</div>

Output:

Fruits
- Apple
- Banana

Vegetables
- Carrot
- Broccoli

x-for with Range

You can loop a fixed number of times without an array:

<div x-data>
  <template x-for="i in 10" :key="i">
    <span x-text="i"></span>
  </template>
</div>

Output: 1 2 3 4 5 6 7 8 9 10

This is useful for pagination controls, star ratings, or any repeated UI pattern that follows a numeric sequence.


Combining Conditionals and Loops

The real power comes when you combine all these directives together. Here’s a classic pattern — a filterable list:

<div x-data="{
  users: [
    { id: 1, name: 'Alice', active: true },
    { id: 2, name: 'Bob', active: false },
    { id: 3, name: 'Charlie', active: true }
  ],
  filter: 'all',
  get filteredUsers() {
    if (this.filter === 'active') return this.users.filter(u => u.active)
    if (this.filter === 'inactive') return this.users.filter(u => !u.active)
    return this.users
  }
}">
  <!-- Filter buttons -->
  <button @click="filter = 'all'" :class="{ active: filter === 'all' }">All</button>
  <button @click="filter = 'active'" :class="{ active: filter === 'active' }">Active</button>
  <button @click="filter = 'inactive'" :class="{ active: filter === 'inactive' }">Inactive</button>

  <!-- User list with animation -->
  <template x-for="user in filteredUsers" :key="user.id">
    <div x-show="filteredUsers.includes(user)" x-transition>
      <span x-text="user.name"></span>
      <span :class="user.active ? 'tag-online' : 'tag-offline'" x-text="user.active ? 'Online' : 'Offline'"></span>
    </div>
  </template>

  <!-- Empty state -->
  <p x-show="filteredUsers.length === 0" style="color: #888;">
    No users match this filter.
  </p>
</div>

What makes this special:

  • filteredUsers is a computed property (a get function) that recalculates automatically when filter or users changes.
  • When you click “Active,” only users with active: true appear.
  • When you click “Inactive,” only users with active: false appear.
  • When no users match, the “No users match” message shows.
  • The x-transition adds a smooth animation when items appear/disappear.

Common Mistakes Beginners Make

1. Using x-if without a <template> tag

<!-- Wrong: x-if must be on a <template> -->
<div x-if="show">Content</div>

<!-- Correct -->
<template x-if="show">
  <div>Content</div>
</template>

Why: <template> is an invisible container. It doesn’t render anything on its own. Alpine uses it as a placeholder for conditional content.

2. Using x-show on a <template> tag

<!-- Wrong: x-show doesn't work on template -->
<template x-show="visible">
  <div>Content</div>
</template>

<!-- Correct: wrap in a div -->
<div x-show="visible">
  <div>Content</div>
</div>

<!-- Or use x-if instead -->
<template x-if="visible">
  <div>Content</div>
</template>

<template> elements can’t have display: none applied to them because the browser doesn’t render them in the first place.

3. Forgetting :key in x-for loops

<!-- Wrong: no key means Alpine re-renders everything -->
<template x-for="item in items">
  <div x-text="item.name"></div>
</template>

<!-- Correct -->
<template x-for="item in items" :key="item.id">

Without keys, Alpine doesn’t know which DOM elements correspond to which data items. Every update causes a full re-render.

4. Mutating arrays in ways Alpine can’t track

Alpine v3 uses JavaScript Proxies and tracks most array mutations automatically:

<!-- These work fine in Alpine 3+ -->
<button @click="items.push('new')">Add</button>
<button @click="items.pop()">Remove last</button>
<button @click="items.splice(0, 1)">Remove first</button>

<!-- This might NOT trigger a re-render -->
<button @click="items[0] = 'changed'">Change first</button>

<!-- This always works -->
<button @click="items = items.map((item, i) => i === 0 ? 'changed' : item)">
  Change first (safe)
</button>

Rule: Mutations (push, pop, splice) are tracked. Direct index assignment might not be. When in doubt, reassign the entire array.

5. Too many nested x-for loops affecting performance

<!-- Fine for small lists -->
<template x-for="category in categories" :key="category.id">
  <template x-for="product in category.products" :key="product.id">
    <template x-for="variant in product.variants" :key="variant.id">
      <div x-text="variant.name"></div>
    </template>
  </template>
</template>

For large datasets (1000+ items), deeply nested loops can slow down rendering. Consider flattening your data or using pagination.


Practice Questions

  1. What is the difference between x-show and x-if? x-show toggles CSS display: none (element stays in DOM). x-if completely adds/removes the element from the DOM using <template>.

  2. Why must x-if be used on a <template> element? Because <template> is invisible by nature — it doesn’t render its children until Alpine processes it. No other HTML element works this way.

  3. What does :key do in x-for loops? It gives Alpine a unique identifier for each item, allowing it to efficiently track additions, removals, and reorders without re-rendering everything.

  4. Can you nest x-for loops? Yes. Each <template x-for> can contain another <template x-for> for hierarchical data like categories → products → variants.

  5. When should you use x-if instead of x-show? When the content is expensive (images, videos, complex components) or rarely shown. The element won’t exist in the DOM until needed, saving memory.

Challenge

Build a “shopping cart” component that:

  • Starts with an array of items (name, price, quantity)
  • Uses x-for to display each item
  • Uses x-show to show a “Cart is empty” message when no items exist
  • Has buttons to increase/decrease quantity for each item
  • Shows a computed total price using a get function
  • Uses x-if for a “Confirm Removal” prompt when removing an item

Try It Yourself

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Alpine.js Filterable List</title>
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
  <div x-data="{
    filter: 'all',
    items: [
      { id: 1, name: 'Learn Alpine.js', done: true },
      { id: 2, name: 'Build a project', done: false },
      { id: 3, name: 'Deploy to production', done: false }
    ],
    get filteredItems() {
      if (this.filter === 'done') return this.items.filter(i => i.done)
      if (this.filter === 'pending') return this.items.filter(i => !i.done)
      return this.items
    }
  }" style="max-width: 400px; margin: 2rem auto; font-family: system-ui;">
    <h2>Task List</h2>
    <div style="margin-bottom: 1rem;">
      <button @click="filter = 'all'" style="margin-right: 0.5rem;">All</button>
      <button @click="filter = 'done'" style="margin-right: 0.5rem;">Done</button>
      <button @click="filter = 'pending'">Pending</button>
    </div>
    <ul>
      <template x-for="item in filteredItems" :key="item.id">
        <li x-show="filteredItems.includes(item)">
          <span x-text="item.name"></span>
          <span x-text="item.done ? '✓' : '○'" style="margin-left: 0.5rem;"></span>
        </li>
      </template>
    </ul>
    <p x-show="filteredItems.length === 0" style="color: #888;">
      No items match this filter.
    </p>
  </div>
</body>
</html>

What to expect: A task list with filter buttons. Click “Done” to see completed tasks, “Pending” for incomplete, “All” to see everything. When no tasks match, the empty state message appears.


FAQ

{< faq >}

What is Alpinejs Conditionals Loops?
Alpinejs Conditionals Loops refers to the core concepts and practices used to build and manage modern web applications. Understanding it is essential for web developers.
Do I need prior experience to learn Alpinejs Conditionals Loops?
Basic familiarity with web development concepts helps, but Alpinejs Conditionals Loops can be learned step by step even as a beginner.
How long does it take to learn Alpinejs Conditionals Loops?
With consistent practice, you can grasp the fundamentals in a few days to a week. Mastery takes ongoing practice and real-world projects.
Where can I use Alpinejs Conditionals Loops in real projects?
Alpinejs Conditionals Loops is used in a wide range of applications — from simple websites to complex enterprise systems, depending on the specific tools and technologies involved.
What are common tools used with Alpinejs Conditionals Loops?
The specific tools depend on the technology stack, but version control (Git), package managers, and testing frameworks are commonly used alongside most development topics.

{< /faq >}

What’s Next

TutorialWhat You’ll Learn
Transitions & PluginsAdvanced animations, plugins, custom directives

Related topics: JavaScript arrays, JavaScript conditionals, Vue.js conditional rendering.

What’s Next

Congratulations on completing this Alpinejs Conditionals Loops tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro