Library / SDK
alpinejs/alpine avatar
alpinejs/alpine

Alpine.js: composing JavaScript behaviour in HTML attributes

A rugged, minimal framework for composing JavaScript behavior in your markup.

31,935 stars1,389 forksHTMLMIT

At a glance

What is it?
Alpine.js is a small framework that keeps state and event handling in your markup instead of a separate component file. The repository is a monorepo of the core plus ten plugins, and the README points nearly all usage questions at the docs site.
Who is it for?
Alpine.js fits server-rendered pages where a handful of interactive islands need state, events or persistence without a build pipeline: drop in the cdn build from packages/alpinejs/dist and start writing attributes. It is the wrong tool for large single-page applications with routing, shared stores across dozens of screens and heavy client-side rendering, because the framework deliberately has no component file model and no router.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Alpine.js is for, and who it is not for

Alpine.js targets the gap between static HTML and a full client-side framework. The README describes it in the repository description as "a rugged, minimal framework for composing JavaScript behavior in your markup", and that phrasing is the design brief: behaviour lives in attributes on existing elements rather than in a separate component file that renders the markup. If your page is produced by a server (Rails, Django, Laravel, a static site generator) and you need a dropdown, a toggle, a tab strip or a form with local validation state, Alpine.js lets you add that without moving the page into a JavaScript build.

The audience is therefore narrow in a useful way. It suits teams that already have HTML they trust and want to annotate it. It does not suit teams building an application where most of the DOM is generated on the client, where routing between screens matters, or where many distant parts of the page read and write the same state. The README does not describe a router or a component composition model, and the package list confirms it: the plugins cover collapse, focus, history, intersect, mask, morph and persist. Those are page-level concerns, not application-level ones.

One caveat worth stating plainly: the README is a contribution guide, not a usage guide. It opens by telling readers to "Go to the Alpine docs for most things" and then explains the monorepo. Anyone evaluating the project from the repository alone will find build instructions and a package table, but no attribute reference. That is a deliberate split, and it means the repository README is the wrong document to read first.

How the monorepo and the build are arranged

The repository is an npm workspaces monorepo. The root package.json declares "workspaces": ["packages/*"] and is marked "private": true, so the root itself is never published. Each package under /packages has its own folder, and the README lists eleven of them: alpinejs for the core, plus collapse, csp, docs, focus, history, intersect, mask, morph and persist.

The build is a single command for the whole tree. The root scripts map "build" to node ./scripts/build.js and "watch" to the same script with a --watch flag. The README states that bundling for Alpine V3 is handled exclusively by ESBuild and that all configuration for these builds lives in scripts/build.js. The root devDependencies pin esbuild at ~0.16.17, which is consistent with that statement.

Each package is expected to emit at least two artefacts into its own dist directory: a cdn build that self-initialises and can be attached with a script tag carrying defer, and a module file for importing as an ES or CommonJS module. The README puts it as "a 'cdn' build that is self-initializing" plus "a module.[esm/cjs].js file that is used for importing as a JS module (cjs for node, esm for everything else)". That dual output is what allows the same source to serve a script tag on a static page and a bundler in a larger project.

Testing is split across two tools. Cypress handles integration tests and Vitest handles unit tests, with test files under /tests/cypress and /tests/vitest. The root scripts expose "cypress": "cypress open" for the interactive runner and "vitest": "vitest run" for a single pass. The presence of jest.config.js in the repository root, alongside vitest.config.js, suggests a migration in progress rather than two parallel suites, though the README only documents the Cypress and Vitest split.

Installing Alpine.js and building the bundle

The README's quickstart is a four-step list, and it is aimed at contributors rather than at someone adding Alpine.js to a site with a package manager. It says to clone the repository locally, run npm install and npm run build, and then include the built file from a script tag. The root package.json confirms both scripts: "build" runs node ./scripts/build.js.

bash
npm install
npm run build

The README states that all package bundles are handled with that same command rather than by running separate builds per package, so one pass produces the cdn and module files for the core and for every plugin. The compiled files land in each package's packages/[package]/dist directory, which means the core build is written to packages/alpinejs/dist.

bash
npm run watch

The root scripts also define "watch" as node ./scripts/build.js --watch, which the README describes as the build command to run while working. The README does not document an npm registry install, a CDN URL, a version pin or a package name to fetch, so if you are not building from a clone, the repository README is silent on how to get the file. It sends you to the docs site instead.

Once the build exists, the README's final quickstart step is to include /packages/alpinejs/dist/cdn.js from a script tag on a webpage. It describes that cdn build as self-initialising and as includable "using the src attribute in a <script defer> tag", so the defer attribute is part of the documented usage and not an optional detail. The README gives no attribute example, no expression syntax and no initialisation call; the docs are where that lives, and the docs are themselves a package in this repository under /packages/docs, which is the tree to read if you want the reference next to the source.

Where the plugin split becomes a real constraint

The single most consequential fact in the README's package table is that persist, mask, morph, intersect, focus, collapse and history are separate packages, not part of the core. A reader who assumes Alpine.js ships with state persistence or input masking will be wrong, and the failure is quiet: an attribute from an unloaded plugin simply does nothing, with no error to point at the missing script.

That has two practical effects. First, every plugin you use is another script tag or another import, and the order in which they load relative to the core matters because the core self-initialises. Second, the plugin set is where the framework's scope is decided. History binds data to query string parameters using the history API, and the README itself flags that the name is "likely to change", which is a signal that this package's surface is less settled than the others. Morph adapts the morphdom approach to update HTML in place, which is the kind of feature you would only reach for if a server is re-rendering fragments.

There is also a Content Security Policy package. The README describes packages/csp as "a repo to provide a 'CSP safe' build of Alpine". Its existence is the clearest statement of a trade-off in the core design: the standard build evaluates expressions written in HTML attributes, and a strict policy that forbids unsafe evaluation will block that. If your deployment enforces such a policy, the csp package is not optional, and the README does not claim the two builds are interchangeable.

Finally, the docs are a package in the same monorepo. The README asks contributors to send documentation updates as pull requests to /packages/docs, and there is an update-docs script in the root package.json. That means documentation and code version together, but it also means a docs fix is a repository contribution rather than a forum post.

Choosing between Alpine.js and a component framework

The natural alternative is a component framework such as Vue or React, where a component owns its template, state and lifecycle in one module. The difference is not size, it is where the boundary sits. In a component framework the unit of reasoning is a file that renders its own markup; in Alpine.js the unit is an element that already exists in server-rendered HTML and carries attributes describing its behaviour. That makes Alpine.js cheap to introduce into an existing page and expensive to use as the primary structure of a large application, because there is no file to open when you want to see everything a feature does.

A second alternative is writing the same behaviour in plain JavaScript with event listeners and data attributes. Alpine.js is essentially a declarative layer over that pattern, and the honest comparison is whether the attribute syntax earns its place over a few lines of addEventListener. For one toggle it probably does not. For a page with a dozen independent interactive regions, the consistency of one attribute vocabulary is the argument.

The repository's own structure supports this reading. There is a benchmarks/ directory at the top level, but the README does not describe what it measures or how to run it, so it is not evidence of anything on its own. The plugin list is the better guide: intersect, focus, collapse, mask, persist and morph are all things you bolt onto existing markup. Nothing in the package table suggests a client-side router, a global store or a server-rendering story.

Maintenance, releases and what the MIT licence means here

The repository is not archived, and the last push was on 2026-09-09. Recent releases are close together: v3.17.0 on 2026-08-30, v3.17.1 on 2026-08-31 and v3.17.2 on 2026-09-07. That cadence is consistent with a project that is still being worked on, and the version line has stayed on 3.x across those releases, so there is no migration story to plan for between them.

The licence is MIT, declared in the repository and shipped as LICENSE.md. For adopters that means the usual permissions to use, modify and redistribute with the copyright notice retained, and no copyleft obligation on your own code. It says nothing about the plugins' readiness or about support; MIT is a grant of rights, not a promise of maintenance. If you need a commitment to fix bugs on a schedule, the licence will not give you one and neither will the README.

Upgrade cost is where the monorepo layout helps. Because every package is built by the same scripts/build.js run and versioned in the same repository, moving the core forward and moving the plugins forward are the same operation: rebuild and redeploy the dist files you actually load. The cost that does not go away is the load-order and presence problem described earlier. An upgrade that changes which plugins exist, or that renames one, will not surface as a build error in a page that loads the cdn build from a script tag. The README's note that the history plugin's name is "likely to change" is the concrete example of that risk. Pin the versions you load and re-check the script tags after each bump.

Editorial conclusion

Alpine.js fits server-rendered pages where a handful of interactive islands need state, events or persistence without a build pipeline: drop in the cdn build from packages/alpinejs/dist and start writing attributes. It is the wrong tool for large single-page applications with routing, shared stores across dozens of screens and heavy client-side rendering, because the framework deliberately has no component file model and no router. Before adopting, verify two things in the docs site: which plugins you actually need (each is a separate package, so persist, mask and morph are not in the core), and whether the Content Security Policy build in packages/csp matters for your deployment, since the standard build evaluates expressions from attributes and that is exactly what a strict CSP forbids.

Frequently asked questions

How do I install Alpine.js?

The README's quickstart is to clone the repository, run npm install and npm run build, then include the file at /packages/alpinejs/dist/cdn.js from a script tag on the page. That cdn build self-initialises, so no extra call is required to start it.

What is Alpine.js written in?

The repository lists HTML as its primary language, and the README describes bundling for Alpine V3 as handled exclusively by ESBuild, with the build configuration in scripts/build.js. The packages themselves are JavaScript modules with both cdn and module builds.

Which plugins ship with Alpine.js?

None of them ship in the core. The README's package table lists collapse, csp, docs, focus, history, intersect, mask, morph and persist as separate packages alongside alpinejs, and each has its own dist directory.

How do I test Alpine.js locally?

The README states that the repository uses Cypress for integration tests and Vitest for unit tests, stored under /tests/cypress and /tests/vitest. The root package.json exposes npm run cypress for the interactive runner and npm run vitest for a single run.

Official sources

  1. alpinejs/alpine on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alpinejs-alpine.svg)](https://hysenlabs.com/projects/alpinejs-alpine)