Loading...

Alpine.js online editor

Write, Run & Share Alpine.js code online using OneCompiler's Alpine.js online editor for free. It's one of the robust, feature-rich online editors for Alpine.js.

About Alpine.js

Alpine.js is a lightweight JavaScript framework for composing behavior directly in your markup. It offers reactive and declarative features similar to Vue or React, but with a much smaller footprint. With just 15 attributes, 6 properties, and 2 methods, it's easy to learn.

Syntax help

Reactive Data with x-data

The x-data attribute defines a component's reactive state directly in HTML.

<!-- Simple reactive data -->
<div x-data="{ message: 'Hello, World!' }">
  <h1 x-text="message"></h1>
</div>

<!-- With methods -->
<div
  x-data="{
  count: 0,
  increment() { this.count++ },
  decrement() { this.count-- }
}"
>
  <button @click="decrement">-</button>
  <span x-text="count"></span>
  <button @click="increment">+</button>
</div>

<!-- Nested objects and arrays -->
<div
  x-data="{
  user: { name: 'John', email: '[email protected]' },
  items: ['Apple', 'Banana', 'Cherry']
}"
>
  <p x-text="user.name"></p>
  <p x-text="items[0]"></p>
</div>

Text & HTML Binding

x-text sets text content (escapes HTML). x-html renders raw HTML.

<div x-data="{ firstName: 'John', lastName: 'Doe' }">
  <span x-text="firstName + ' ' + lastName"></span>
  <span x-text="`Welcome, ${firstName}!`"></span>
</div>

<!-- HTML binding (use with trusted content only) -->
<div x-data="{ content: '<strong>Bold</strong> text' }">
  <div x-html="content"></div>
</div>

Event Handling with @

The @ shorthand (or x-on) attaches event listeners with optional modifiers.

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

<!-- Event modifiers -->
<form @submit.prevent="console.log('submitted')">
  <button type="submit">Submit</button>
</form>

<button @click.once="alert('Only once!')">Click Once</button>
<input @input.debounce.300ms="search($event.target.value)" />

<!-- Keyboard events -->
<input @keydown.enter="submit" @keydown.escape="cancel" />

Attribute Binding with :

The : shorthand (or x-bind) dynamically binds attributes.

<div x-data="{ imageUrl: 'photo.jpg', isDisabled: true }">
  <img :src="imageUrl" alt="Photo" />
  <button :disabled="isDisabled">Disabled</button>
</div>

<!-- Class binding -->
<div x-data="{ isActive: true, hasError: false }">
  <div :class="{ 'active': isActive, 'error': hasError }">Content</div>
  <div :class="isActive ? 'bg-green' : 'bg-gray'">Content</div>
</div>

<!-- Style binding -->
<div x-data="{ color: 'red', size: 20 }">
  <p :style="{ color: color, fontSize: size + 'px' }">Styled</p>
</div>

Conditional Rendering

x-show toggles CSS display. x-if adds/removes elements from DOM.

<!-- x-show: toggles display -->
<div x-data="{ isVisible: true }">
  <button @click="isVisible = !isVisible">Toggle</button>
  <div x-show="isVisible" x-transition>Visible content</div>
</div>

<!-- x-if: must be on <template> tag -->
<div x-data="{ showForm: false }">
  <button @click="showForm = !showForm">Toggle Form</button>
  <template x-if="showForm">
    <form>
      <input placeholder="Name" />
      <button>Submit</button>
    </form>
  </template>
</div>

<!-- x-else -->
<div x-data="{ loggedIn: false }">
  <template x-if="loggedIn">
    <p>Welcome back!</p>
  </template>
  <template x-else>
    <p>Please log in</p>
  </template>
</div>

List Rendering with x-for

x-for renders lists from arrays. Must be on a <template> element.

<div x-data="{ items: ['Apple', 'Banana', 'Cherry'] }">
  <ul>
    <template x-for="item in items">
      <li x-text="item"></li>
    </template>
  </ul>
</div>

<!-- With index -->
<div x-data="{ colors: ['Red', 'Green', 'Blue'] }">
  <template x-for="(color, index) in colors">
    <p x-text="`${index + 1}. ${color}`"></p>
  </template>
</div>

<!-- With key for dynamic lists -->
<div
  x-data="{ todos: [{ id: 1, text: 'Learn Alpine' }, { id: 2, text: 'Build app' }] }"
>
  <template x-for="todo in todos" :key="todo.id">
    <div x-text="todo.text"></div>
  </template>
</div>

Two-Way Binding with x-model

x-model creates two-way binding between inputs and component state.

<!-- Text input -->
<div x-data="{ name: '' }">
  <input x-model="name" placeholder="Enter name" />
  <p x-text="'Hello, ' + (name || 'stranger')"></p>
</div>

<!-- Checkbox -->
<div x-data="{ agreed: false }">
  <label><input type="checkbox" x-model="agreed" /> I agree</label>
  <button :disabled="!agreed">Submit</button>
</div>

<!-- Radio buttons -->
<div x-data="{ color: '' }">
  <label><input type="radio" value="red" x-model="color" /> Red</label>
  <label><input type="radio" value="blue" x-model="color" /> Blue</label>
  <p x-text="'Chosen: ' + color"></p>
</div>

<!-- Select -->
<div x-data="{ country: 'us' }">
  <select x-model="country">
    <option value="us">United States</option>
    <option value="uk">United Kingdom</option>
  </select>
</div>

<!-- Modifiers -->
<input x-model.lazy="value" placeholder="Updates on blur" />
<input x-model.number="age" type="number" />
<input x-model.debounce.500ms="search" />

Transitions

x-transition adds smooth enter/leave transitions with x-show.

<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show="open" x-transition>Smooth fade</div>
</div>

<!-- Transition modifiers -->
<div x-show="show" x-transition.duration.500ms>500ms transition</div>
<div x-show="show" x-transition.opacity>Fade only</div>
<div x-show="show" x-transition.scale.origin.top>Scale from top</div>

Lifecycle with x-init & x-effect

x-init runs on initialization. x-effect runs reactively when dependencies change.

<!-- x-init -->
<div x-data="{ message: '' }" x-init="message = 'Initialized!'">
  <p x-text="message"></p>
</div>

<!-- Fetch data on init -->
<div
  x-data="{ users: [] }"
  x-init="users = await (await fetch('/api/users')).json()"
>
  <template x-for="user in users">
    <p x-text="user.name"></p>
  </template>
</div>

<!-- x-effect: runs when dependencies change -->
<div x-data="{ count: 0 }">
  <button @click="count++">Increment</button>
  <div x-effect="console.log('Count:', count)"></div>
</div>

Magic Properties

Alpine provides $el, $refs, $watch, $dispatch, and $nextTick.

<!-- $refs: reference elements -->
<div x-data>
  <input x-ref="input" placeholder="Type here" />
  <button @click="$refs.input.focus()">Focus</button>
</div>

<!-- $watch: watch for changes -->
<div x-data="{ count: 0 }" x-init="$watch('count', val => console.log(val))">
  <button @click="count++">Count: <span x-text="count"></span></button>
</div>

<!-- $dispatch: custom events -->
<div x-data @notify="alert($event.detail.message)">
  <button @click="$dispatch('notify', { message: 'Hello!' })">Notify</button>
</div>

<!-- $nextTick: run after DOM updates -->
<div x-data="{ items: [] }">
  <button @click="items.push('new'); $nextTick(() => console.log('updated'))">
    Add Item
  </button>
</div>

Reusable Components with Alpine.data

Define reusable component logic with Alpine.data().

<script>
  document.addEventListener("alpine:init", () => {
    Alpine.data("dropdown", () => ({
      open: false,
      toggle() {
        this.open = !this.open
      },
      close() {
        this.open = false
      },
    }))

    Alpine.data("counter", (initialCount = 0) => ({
      count: initialCount,
      increment() {
        this.count++
      },
      decrement() {
        this.count--
      },
    }))
  })
</script>

<div x-data="dropdown">
  <button @click="toggle">Menu</button>
  <div x-show="open" @click.outside="close">
    <a href="#">Item 1</a>
    <a href="#">Item 2</a>
  </div>
</div>

<div x-data="counter(10)">
  <button @click="decrement">-</button>
  <span x-text="count"></span>
  <button @click="increment">+</button>
</div>

Global State with Alpine.store

Share state across components with Alpine.store().

<script>
  document.addEventListener("alpine:init", () => {
    Alpine.store("darkMode", {
      on: false,
      toggle() {
        this.on = !this.on
      },
    })

    Alpine.store("cart", {
      items: [],
      add(item) {
        this.items.push(item)
      },
      get total() {
        return this.items.reduce((sum, i) => sum + i.price, 0)
      },
    })
  })
</script>

<button x-data @click="$store.darkMode.toggle()">Toggle Dark Mode</button>
<div x-data :class="{ 'dark': $store.darkMode.on }">Theme-aware content</div>

<div x-data>
  <span x-text="$store.cart.items.length + ' items'"></span>
  <span x-text="'$' + $store.cart.total"></span>
</div>

Plugins

Alpine has plugins for Mask, Intersect, Persist, and Collapse.

<!-- Mask: input formatting -->
<input x-data x-mask="(999) 999-9999" placeholder="Phone" />

<!-- Persist: save to localStorage -->
<div x-data="{ count: $persist(0) }">
  <button @click="count++">Count: <span x-text="count"></span></button>
</div>

<!-- Collapse: smooth height animations -->
<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show="open" x-collapse>
    <p>Smoothly expanding content</p>
  </div>
</div>