Alpine.js is a minimal, declarative JavaScript framework by Caleb Porzio (2019). At just 7.1 kB gzipped — no build step, no virtual DOM — it adds reactive behaviour directly to HTML markup. It’s built for server-rendered apps and static sites, not as a React or Vue replacement.
- 1 Alpine.js is as small as just 7.1 kB gzipped, approximately 18x smaller than React (~130 kB minified) and 13x smaller than jQuery (~90 kB minified).
- 2 It has over 30k+ GitHub stars and is downloaded about 433K+ times a week by npm.
- 3 Alpine.js comes with 15 directives, 6 magic properties, and 2 methods with the full public API.
- 4 It has no build step - just a single <script> tag and you are off.
- 5 Alpine.js is not an alternative to React or Vue in larger single-page applications, and is intended to be used to progressively enhance pages rendered on the server.
What Is Alpine.js?
Alpine.JS is a lightweight, open source JavaScript framework that offers declarative/reactive interactivity in the form of HTML. It was written by Caleb Porzio and initially published in 2019, and is frequently called the jQuery of the modern web, in that it is simple to include without a build system, but based on modern principles of reactivity instead of imperative manipulation of the DOM.
In contrast to React and Vue, which work with a virtual DOM and componentize it, Alpine.js works with the real DOM itself. It scans the page to find x- prefixed attributes and replaces them with reactive behavior, but does not replace HTML server transmissions to browser.
This makes Alpine.js uniquely well-suited for:
- Server-rendered frameworks such as Laravel (Blade), Ruby on Rails (ERB), Django, and AdonisJS
- Static sites built with 11ty, Hugo, or Jekyll that need a sprinkle of JavaScript
- WordPress and Shopify themes requiring lightweight interactivity without a bundler
- Prototyping — where speed of implementation matters more than architectural purity
Alpine.js vs React vs Vue: At a Glance
| Factor | Alpine.js | React | Vue 3 |
|---|---|---|---|
| Bundle size (gzipped) | ~7.1 kB | ~130 kB | ~34 kB |
| Build step required | No | Yes | Optional |
| Virtual DOM | No | Yes | Yes |
| Weekly npm downloads | ~433K+ | ~20 million+ | ~7 million+ |
| GitHub stars | 30,000+ | 220,000+ | 47,000+ |
| Best for | Server-rendered pages, static sites | Large SPAs, complex UIs | Medium apps, progressive adoption |
| Learning curve | Very low | Medium-high | Low-medium |
| State management | Local component state | useState / Redux / Zustand | Pinia / Vuex |
| Full SPA support | Not intended | Yes | Yes |
Sources: npm trends (March 2025), GitHub repository data (March 2025), bundlephobia.com
How to Install Alpine.js
Alpine.js can be added to any project in two ways.
Option 1: CDN (Recommended for most use cases)
Add a single script tag to your HTML <head>. The defer attribute is required so Alpine initializes after the DOM is parsed:
1 | <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> |
No npm, no Webpack, no Vite — that is the complete installation.
Option 2: npm (For project bundlers)
1 | npm install alpinejs |
Then initialize it in your JavaScript entry point:
1 2 3 | import Alpine from 'alpinejs' window.Alpine = Alpine Alpine.start() |
The window.Alpine = Alpine assignment is important if you want to access the Alpine instance from browser DevTools or external scripts.
Core Directives: The Full API in Practice
Alpine.js ships with exactly 15 directives (HTML attributes prefixed with x-). Here are the most commonly used ones, with working examples:
x-data — Declare a Component
x-data defines a new Alpine component and its reactive state. Any element with x-data becomes an isolated reactive scope:
1 2 3 | <div x-data="{ open: false, count: 0 }"> <!-- All children can access 'open' and 'count' --> </div> |
x-bind — Bind HTML Attributes
x-bind (shorthand: : ) dynamically sets any HTML attribute based on the component’s state:
1 2 3 | <button :class="open ? 'bg-blue-500' : 'bg-gray-400'"> Toggle </button> |
x-on — Attach Event Listeners
x-on (shorthand: @) attaches event listeners to elements:
1 | <button @click="open = !open">Toggle Menu</button> |
x-show — Conditional Visibility
x-show toggles the CSS display property based on an expression. Unlike x-if, it does not remove the element from the DOM — it simply hides it:
1 2 3 | <div x-show="open" x-transition> This content is visible when open is true. </div> |
x-model — Two-Way Data Binding
x-model syncs an input element’s value with a reactive data property:
1 2 | <input type="text" x-model="searchQuery"> <p x-text="'You typed: ' + searchQuery"></p> |
x-for — Loop Over Arrays
x-for iterates over an array and renders a template for each item:
1 2 3 | <template x-for="item in items" :key="item.id"> <li x-text="item.name"></li> </template> |
A Real-World Example: Interactive Todo List
The following example combines x-data, x-model, x-for, x-on, and x-transition to build a functional, animated todo list — in pure HTML, with no build step:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <div x-data="{ todos: ['Learn Alpine.js', 'Ship a project'], newTodo: '', addTodo() { if (this.newTodo.trim()) { this.todos.push(this.newTodo.trim()); this.newTodo = ''; } } }"> <form @submit.prevent="addTodo()"> <input type="text" x-model="newTodo" placeholder="Add a todo..." class="border rounded px-3 py-2" > <button type="submit" class="ml-2 px-4 py-2 bg-blue-500 text-white rounded"> Add </button> </form> <ul class="mt-4 space-y-2"> <template x-for="todo in todos" :key="todo"> <li x-text="todo" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -translate-y-1" x-transition:enter-end="opacity-100 translate-y-0" class="p-2 bg-gray-50 rounded" ></li> </template> </ul> </div> |
This pattern — reactive state declared inline with x-data, form handling with @submit.prevent, and animated list rendering with x-for + x-transition — covers 80% of real-world Alpine.js use cases.
Alpine.js Plugins (Extended Ecosystem)
Alpine.js v3 introduced an official plugin architecture. The following first-party plugins extend the core without bloating the default bundle:
| Plugin | What it adds |
|---|---|
| @alpinejs/persist | Persists x-data state to localStorage across page loads |
| @alpinejs/intersect | Triggers callbacks when elements enter or leave the viewport |
| @alpinejs/resize | Reacts to element size changes via ResizeObserver |
| @alpinejs/focus | Manages focus trapping for modals and dropdowns |
| @alpinejs/collapse | Smooth height animation for accordion/collapsible elements |
| @alpinejs/morph | Intelligently updates the DOM while preserving existing state |
Each plugin is tree-shakable and loaded independently, so only the functionality your project uses is included in the final bundle.
When to Use Alpine.js (and When Not To)
Use Alpine.js when:
- You are dealing with a server-rendered application (Laravel, Rails, Django) and have to implement some UI interaction that will not require overriding the HTML given by the server.
- Your page requires isolated interactive components — a dropdown, a modal, a tab panel, a form with validation — rather than a full client-side router.
- You do not want to go through a build phase and deliver straight to production out of raw HTML.
- You are maintaining a WordPress, Shopify, or static site and React or Vue feels like too much infrastructure.
Do not use Alpine.js when:
- Your application is a full single-page application with client-side routing — React, Vue or SvelteKit should be used instead.
- You need complex, shared global state across many components — Alpine’s state is deliberately local, not designed for large state graphs.
- Your team already has a React or Vue codebase — there is no advantage to mixing frameworks.
As the official Alpine.js documentation puts it: “Think of it like jQuery for the modern web. Plop in a script tag and get going.”
Performance: What 7.1 kB Actually Means
Alpine.js’s minimal footprint has a measurable impact on Core Web Vitals, particularly Time to Interactive (TTI) and Total Blocking Time (TBT) — two metrics Google uses as ranking signals since the 2021 Page Experience update.
A page loading React (130 kB), even with code splitting, requires the browser to parse, compile, and execute significantly more JavaScript than a page using Alpine.js (7.1 kB). On a mid-range mobile device on a 4G connection, this difference can represent 300–600ms of additional processing time — enough to shift a page from a “Good” to a “Needs Improvement” Core Web Vitals rating.
For content sites, blogs, marketing pages, and documentation — where most pages are statically rendered and only need lightweight interactivity — Alpine.js is frequently the correct technical choice over a full SPA framework.
Conclusion
Alpine.js occupies an actual and unmet niche in the JavaScript landscape: a lightweight no-build-step library to make server-rendered HTML reactive. With a size of 7.1 kB gzipped, and 15 directives and a star community size of 30,000+, it has proven itself the default TALL stack, and any project where React or Vue would have made architectural overkill.
Alpine.js is probably the right tool to use in the case of your project is a server-rendered application that requires dropdowns or modals or tabs or form interactions, and you do not require client-side routing or a complicated global state. React, Vue, or SvelteKit should be used in case you are developing a large and client-intensive single-page application.
Sources
- Alpine.js official documentation: alpinejs.dev
- Alpine.js GitHub repository: github.com/alpinejs/alpine
- Bundle size data: bundlephobia.com
- Medium: “Alpine.js: The Minimalist JavaScript Framework for Modern Web Development” (Zulfikar Ditya, March 2025)
