AlpineJS: Lightweight JavaScript Framework That Feels Like Vue
Tired of jQuery's mess but don't need a full SPA? Discover how AlpineJS delivers Vue-like reactivity in 15KB, making web development fun again for Laravel projects and beyond.
I wasn’t looking for a new JavaScript framework. I had Vue for complex stuff, jQuery for legacy projects (unfortunately), and a growing sense that there should be something better for everything in between.
Then I found AlpineJS, and it turned out to be exactly that tool.
For years I’d been stuck in an annoying middle ground. jQuery felt like writing with a broken pencil — you could get the job done, but everything took twice as long and looked messy. Full-blown Vue or React SPAs were overkill for most projects. I didn’t need a router, virtual DOM, or 200KB of dependencies just to make a modal appear.
I wanted something modern and reactive that didn’t make me dread opening the JavaScript file.
The problem: we’ve been over-engineering everything
Most websites don’t need to be SPAs. You’ve got a Laravel backend serving HTML, maybe a dashboard with some filters, a few interactive forms, some modals. Classic website stuff.
But somehow the industry convinced us we needed to set up build pipelines, configure bundlers, manage state, handle routing, and ship hundreds of kilobytes of JavaScript just to toggle a modal or filter a list.
jQuery was the old solution, but jQuery code ages badly. It’s imperative, messy, and after a few months even you can’t figure out what’s happening in that callback hell you created.
Enter AlpineJS: Vue’s smaller sibling
AlpineJS is 15KB with no build step required. Drop it in your HTML and start writing reactive JavaScript that actually makes sense.
The closest comparison: it’s Vue’s Composition API running directly in your HTML.
Coming from Vue, Alpine feels immediately familiar. The directives, the reactive mindset — it’s all there. But instead of building a separate SPA, you’re adding interactivity to server-rendered HTML exactly where you need it.
Code that makes sense
A simple counter:
<div x-data="{ count: 0 }">
<button @click="count++">Increment</button>
<span x-text="count"></span>
</div>
Declare reactive data with x-data, bind your click event with @click, display it with x-text. If you’ve written Vue, this should look familiar. No new Vue(), no component registration, no setup file.
Looping through data:
<div x-data="{
items: ['Coffee', 'Tea', 'Beer']
}">
<template x-for="item in items" :key="item">
<li x-text="item"></li>
</template>
</div>
It’s v-for from Vue, just prefixed with x- instead of v-. Same mental model, same reactivity.
Building a modal:
<div x-data="{ open: false }">
<button @click="open = true">Open Modal</button>
<div x-show="open"
@click.away="open = false"
x-transition>
<div class="modal-content" @click.stop>
<h2>Modal Title</h2>
<p>Your content here</p>
<button @click="open = false">Close</button>
</div>
</div>
</div>
A functional modal with click-away-to-close and transitions, all in readable markup. No jQuery selector soup.
The real-world test: Laravel login modals
Here’s where Alpine proved itself. I was working on a Laravel project with static login and signup forms — separate pages, clunky redirects, no UX polish. The client wanted modals with form validation and the ability to switch between login and signup without losing context.
With jQuery that would’ve been a mess of DOM manipulation and state tracking. With a full Vue SPA we’d be fighting against Blade templates.
<div x-data="{
showAuth: false,
mode: 'login',
email: '',
password: '',
loading: false
}" x-init="
if (localStorage.getItem('pendingAction')) {
showAuth = true;
mode = localStorage.getItem('authMode') || 'login';
}
">
<button @click="showAuth = true; mode = 'login'">
Login
</button>
<div x-show="showAuth" x-transition class="modal">
<div class="modal-content" @click.away="showAuth = false">
<div x-show="mode === 'login'">
<h2>Login</h2>
<form @submit.prevent="handleLogin">
<!-- form fields -->
</form>
<a @click="mode = 'signup'">Need an account?</a>
</div>
<div x-show="mode === 'signup'">
<h2>Sign Up</h2>
<form @submit.prevent="handleSignup">
<!-- form fields -->
</form>
<a @click="mode = 'login'">Already registered?</a>
</div>
</div>
</div>
</div>
Modal state management, form switching, API calls, loading states — all without leaving the HTML. The code stayed maintainable, and we could persist state using Alpine’s plugins.
The plugins
Persist: localStorage without the boilerplate
<div x-data="{
mode: Alpine.$persist('login').as('authMode'),
pendingAction: Alpine.$persist(null).as('userAction')
}">
mode and pendingAction now save to localStorage automatically and survive page reloads. No manual localStorage.setItem() calls, no JSON parsing.
Alpine Store: global state
For shared state across multiple components:
Alpine.store('auth', {
user: null,
isLoggedIn: false,
login(userData) {
this.user = userData;
this.isLoggedIn = true;
}
});
Access it anywhere:
<div x-show="$store.auth.isLoggedIn">
Welcome, <span x-text="$store.auth.user.name"></span>!
</div>
Vuex/Pinia-lite, without the boilerplate. For most projects, this is the right amount of state management.
When Alpine fits (and when it doesn’t)
Alpine isn’t a replacement for a React admin dashboard or a complex Vue SPA. It’s not trying to be.
Alpine works well for:
- Interactive forms with validation
- Modals, dropdowns, and UI components
- Filtering and sorting without page reloads
- Dashboard widgets with real-time updates
- Progressive enhancement for server-rendered apps
Alpine is the wrong tool for:
- Complex client-side routing
- Heavy data tables with thousands of rows
- Real-time collaborative features
- SEO-critical SPAs
The sweet spot is Laravel projects, static sites that need some interactivity, marketing sites, internal tools — probably 80% of what gets built.
Why small frameworks matter
AlpineJS is built by Caleb Porzio, the same developer behind Laravel Livewire. It’s not a VC-backed project chasing funding rounds. The scope is deliberately narrow: do one thing, stay small.
15KB. No dependencies. Works anywhere.
Build without the overhead
For your next website — the one that’s mostly server-rendered with some interactive bits — Alpine is worth trying.
No build tools failing, no dependency conflicts, no “works on my machine” debugging. Just HTML with a layer of reactivity where you need it.
jQuery is effectively dead for new projects. Full SPAs are overkill for most sites. Alpine sits in that gap and does the job without the ceremony.
Resources:
- AlpineJS: https://alpinejs.dev/
- Persist Plugin: https://alpinejs.dev/plugins/persist
- Alpine Store: https://alpinejs.dev/globals/alpine-store
Be awesome.
Keep building magic. ✊
Petar 🥃