Skip to content
intermediate Phase 57 · Modern Frontend

Alpine.js in Magento

Using Alpine.js in Magento 2: replacing KnockoutJS, reactive UI patterns, and component implementation

45m
0 problems
Topic Progress 0%

Alpine.js Basics

What is Alpine.js?

Alpine.js is a lightweight JavaScript framework for adding interactivity to HTML.

Basic Syntax

<!-- x-data: Initialize component -->
<div x-data="{ count: 0 }">
    <button @click="count++">Count: <span x-text="count"></span></button>
</div>

Directives

Directive Purpose
x-data Initialize component state
x-bind (:) Bind attribute
x-on (@) Event listener
x-text Set text content
x-html Set HTML content
x-show Show/hide element
x-if Add/remove element
x-for Loop through array
x-model Two-way binding
x-init Initialize on load

Complete Example

<div x-data="{ items: [], newItem: '' }" x-init="items = await fetch('/api/items').then(r => r.json())">
    <input type="text" x-model="newItem">
    <button @click="items.push(newItem); newItem = ''">Add</button>
    
    <template x-for="item in items" :key="item">
        <div x-text="item"></div>
    </template>
</div>

Reactive UI Patterns

Toggle Component

<div x-data="{ open: false }">
    <button @click="open = !open" x-text="open ? 'Close' : 'Open'">
    </button>
    
    <div x-show="open" x-transition>
        Toggle content
    </div>
</div>

Form Handling

<div x-data="{
    form: { email: '', password: '' },
    loading: false,
    async submit() {
        this.loading = true;
        await fetch('/login', {
            method: 'POST',
            body: JSON.stringify(this.form)
        });
        this.loading = false;
    }
}">
    <form @submit.prevent="submit">
        <input type="email" x-model="form.email">
        <input type="password" x-model="form.password">
        <button type="submit" :disabled="loading">
            <span x-show="!loading">Login</span>
            <span x-show="loading">Loading...</span>
        </button>
    </form>
</div>

Shopping Cart

<div x-data="{
    cart: [],
    get total() {
        return this.cart.reduce((sum, item) => sum + item.price * item.qty, 0);
    },
    add(product) {
        this.cart.push({...product, qty: 1});
    },
    remove(index) {
        this.cart.splice(index, 1);
    }
}">
    <div x-text="'Cart: ' + cart.length + ' items'"></div>
    <div x-text="'Total: $' + total.toFixed(2)"></div>
    
    <template x-for="(item, index) in cart" :key="index">
        <div>
            <span x-text="item.name"></span>
            <button @click="remove(index)">Remove</button>
        </div>
    </template>
</div>

Component Patterns

Dropdown Component

<div x-data="{ open: false }" @click.away="open = false">
    <button @click="open = !open">
        Menu
        <svg x-show="open" class="rotate-180">...</svg>
    </button>
    
    <div x-show="open"
         x-transition:enter="transition ease-out duration-200"
         x-transition:enter-start="opacity-0 scale-95"
         x-transition:enter-end="opacity-100 scale-100"
         x-transition:leave="transition ease-in duration-150"
         x-transition:leave-start="opacity-100 scale-100"
         x-transition:leave-end="opacity-0 scale-95">
        Menu items
    </div>
</div>

Modal Component

<div x-data="{ open: false }">
    <button @click="open = true">Open Modal</button>
    
    <div x-show="open" class="fixed inset-0 bg-black bg-opacity-50">
        <div class="bg-white rounded p-6 max-w-md mx-auto mt-20">
            <h2 class="text-xl font-bold">Modal Title</h2>
            <p>Modal content</p>
            <button @click="open = false">Close</button>
        </div>
    </div>
</div>

Tab Component

<div x-data="{ activeTab: 'tab1' }">
    <div class="flex border-b">
        <button @click="activeTab = 'tab1'"
                :class="activeTab === 'tab1' ? 'border-b-2 border-blue-500' : ''">
            Tab 1
        </button>
        <button @click="activeTab = 'tab2'"
                :class="activeTab === 'tab2' ? 'border-b-2 border-blue-500' : ''">
            Tab 2
        </button>
    </div>
    
    <div x-show="activeTab === 'tab1'">Content 1</div>
    <div x-show="activeTab === 'tab2'">Content 2</div>
</div>

Migrating from KnockoutJS

KnockoutJS to Alpine.js

KnockoutJS Alpine.js
ko.observable() x-data property
ko.computed() Getter in x-data
data-bind="text" x-text
data-bind="visible" x-show
data-bind="click" @click
data-bind="foreach" x-for
data-bind="value" x-model

Migration Example

KnockoutJS

<div data-bind="scope: 'cart'">
    <span data-bind="text: items().length"></span>
    <button data-bind="click: addItem">Add</button>
</div>

<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "component": "cart"
        }
    }
}
</script>

Alpine.js

<div x-data="{ items: [], addItem() { this.items.push({}) } }">
    <span x-text="items.length"></span>
    <button @click="addItem">Add</button>
</div>

Key Differences

  • No build step: Alpine.js works directly in HTML
  • No components: State lives in x-data
  • Simpler syntax: Directives are intuitive
  • Lighter weight: ~15KB vs 500KB+

Quiz

1. What directive initializes Alpine.js state?

Question 1 options

2. What replaces KnockoutJS data-bind="text"?

Question 2 options

3. How do you handle form submission?

Question 3 options

Flashcards

Question

What is Alpine.js?

Answer

A lightweight JavaScript framework for adding interactivity

Question

What initializes Alpine state?

Answer

x-data directive

Question

How do you bind events?

Answer

@click, @submit, @change, etc.

Question

How do you show/hide elements?

Answer

x-show directive

Question

How do you loop through arrays?

Answer

x-for directive

Revision Notes

Key Takeaways

  • 1. Alpine.js provides reactive UI without build step
  • 2. x-data initializes component state
  • 3. Directives replace KnockoutJS bindings
  • 4. No RequireJS or build tools required
  • 5. Much lighter than KnockoutJS

Interview Tips

  • Explain Alpine.js vs KnockoutJS differences
  • Know the common Alpine.js directives
  • Discuss migration patterns
  • Be ready to create Alpine.js components

Cheat Sheet

Directives:
  x-data: Initialize state
  x-text: Set text
  x-html: Set HTML
  x-show: Show/hide
  x-for: Loop
  x-model: Two-way binding
  @click: Event listener

Migration:
  data-bind="text" → x-text
  data-bind="visible" → x-show
  data-bind="foreach" → x-for