alpinejs
Skill2 starsChanged 30 days agoMITView on GitHub
Alpine.js v3 traps, APIs and plugins. Use when writing, reviewing or debugging Alpine markup (`x-data`, `x-*` directives, `@`/`:` shorthands, `$` magics), registering `Alpine.data`/`Alpine.store`/`Alpine.bind`, using an official plugin (persist, collapse, focus, intersect, mask, morph, anchor, sort, resize), writing custom directives or magics, running Alpine under a strict CSP, or when an Alpine component renders nothing, flashes, or ignores its state.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- brettatoms/agent-skillstheir own version, first seen Jan 30, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- brettatoms/agent-skills★ 3same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository7 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- answer-reviewersSkill
- capture-search-demandSkill
- decision-recapSkill
- fix-context-driftSkill
- pdf-to-markdownSkill
- resolve-stacked-conflictsSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 29
Plugin scripts load before core: Every @alpinejs/* script tag goes above the core tag, all with defer.
- Very specific: line 61
Stores are not components: No magics on this and no destroy().
- Very specific: line 89
x-model.enterdoes not stop a form from submitting: Add @submit.prevent on the form. - Distinctive: line 93
Read the file whose branch the task is on.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Alpine.js
You already know Alpine's directives. This file holds the traps: places where code that reads as correct Alpine silently does nothing, runs twice, or leaks. Check every piece of Alpine you write or review against every trap below; a component is done when none of them applies. Exact syntax lives in the references at the end.
Setup traps
Registration happens before Alpine starts. With a script tag, Alpine.data, Alpine.store, Alpine.bind, Alpine.directive and Alpine.magic run inside an alpine:init listener, because the deferred core starts before a later inline script would run:
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({ open: false }))
})
</script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.17.4/dist/cdn.min.js"></script>With a bundle, register everything and call Alpine.plugin(...) between the import and Alpine.start(), and call Alpine.start() exactly once per page.
Pin the version in production. @3.x.x in a CDN URL floats to whatever ships next; write the exact version (currently 3.17.4) and pin plugins to the same one.
Plugin scripts load before core. Every @alpinejs/* script tag goes above the core tag, all with defer. Plugin directives and magics (x-collapse, x-intersect, x-trap, x-mask, x-anchor, x-sort, x-resize, $persist, $focus, $money) are inert attributes or undefined without their plugin.
<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
alpinejs/src/lifecycle.js,warnAboutMissingPlugins): on start, Alpine logsfound "[x-anchor]", but missing anchor pluginforx-anchor,x-sort,x-dialogandx-popoveronly. Every other missing plugin fails silently, so check the script order before debugging the expression.
Directives need an x-data on the element or an ancestor. Outside one, @click, :class and x-show do nothing; add a bare x-data. x-init is the only exception.
x-cloak needs its CSS. The attribute hides nothing until the page carries [x-cloak] { display: none !important; }. Put x-cloak on anything that starts hidden (x-show with a false initial value) or whose text Alpine fills in, so it does not flash on load.
A strict CSP blocks the standard build. A policy without 'unsafe-eval' needs @alpinejs/csp, which parses only a subset of expressions: no globals, arrow functions or template literals in attributes. See references/csp.md.
Template traps
x-for,x-ifandx-teleportgo on a<template>with exactly one root element. Wrap siblings in a single element.<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
directives/x-if.js,x-for.js,x-teleport.js): each clonesel.content.firstElementChild. A second root element is dropped without an error; only a warning fires when the directive is not on a<template>.:keyis a stable id from the data (item.id), never the loop index when the list reorders, filters or has items removed.x-transitionanimatesx-showonly. Onx-ifit does nothing; switch tox-show, orx-collapsefor height.x-showversusx-if:x-showkeeps the node (its state, listeners and input values survive);x-ifdestroys and rebuilds it, runningdestroy(). Pickx-ifwhen the hidden branch must not exist in the DOM (heavy content,requiredinputs that would block a hidden form).x-htmlis for trusted strings only. Anything user-supplied goes throughx-text.Teleported content keeps its scope but not its DOM position. Its events bubble from the target (
body), not from the component, and@click.outsideon the component treats clicks inside the teleported modal as outside. Listen on the<template x-teleport>itself, which receives forwarded copies of the content's events.<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
utils/on.js):.outsidereturns early only whenel.contains(e.target)is true, the target is disconnected, the element has no size, orel._x_isShown === false. Nothing checks_x_teleportBack, so a click inside teleported content counts as outside the component that owns it.
Data traps
init()is called automatically.x-init="init()"next to anx-datathat definesinit()runs it twice. Order: the data'sinit(), thenx-init.- Getters are not cached. They rerun on every read; keep them cheap, and store anything expensive in a property updated from
$watchorx-effect. - Methods reach state through
this. A bareitemsinside a getter or method of an object literal is aReferenceError;this.itemsalso reaches parent scopes. Arrow functions losethis: writetoggle() {}, nevertoggle: () => {}. - Providers that use
thiswhile building their object must befunctions.Alpine.data('x', function () { return { open: this.$persist(false) } }); an arrow provider has nothis. Stores are not components. No magics on
thisand nodestroy(); react to a store's own changes withAlpine.effectin itsinit().<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
alpinejs/src/store.js):Alpine.store()wraps the value inreactive(), runs interceptors (which is why$persistworks in a store) and calls an owninit()once. It never callsinjectMagics(whichx-datadoes) and registers no cleanup, sothis.$watchisundefinedanddestroy()is never called.$refsnames are static and scoped to their component.:x-refdoes not evaluate: the ref is named by the literal string. A parent's$refsdoes not see refs under a nestedx-data, and refs insidex-if/x-fortemplates exist only once rendered. In anx-for, a repeated ref name keeps the last element.<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
directives.js,directives/x-ref.js,magics/$refs.js):x-refregisters through aninlinehandler, which runs while Alpine walks the tree, whilex-data(and soinit()) is deferred until the walk ends. Static refs anywhere in the component are therefore already available in the parent'sinit(). Each ref is stored onclosestRoot(el), and$refsmerges only the ref objects of the element's own ancestors.$watchis lazy and must not feed itself. It skips the initial value (usex-effectto run up front), and mutating the watched object inside its own callback loops forever.x-modelyields strings, including fromtype="number"andrange. Add.numberor.boolean, ortotal + qtyconcatenates.<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
directives/x-model.js,getInputValue): the value isevent.target.value, with no casting based onel.type..numberparses withparseFloat: an empty input givesnull, and text that is not a number stays the raw string. So.numbervalues still need anullcheck.x-modelableclones as JSON.File,Date,Map,Setand class instances lose themselves crossing it; dispatch aninputevent instead.$persistkeys collide, and storage beats code. The default key is the property name, shared by every component on the origin; give each persisted value.as('unique-key'). Once a value is stored, changing its default in code does nothing for returning visitors; rename the key.<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
persist/src/index.js): the key isalias || '_x_' + path. A stored value isJSON.parsed and replaces the initial value. Setting the property toundefinedremoves the key. WhenlocalStoragethrows (private mode, blocked storage),$persistfalls back to an in-memoryMapand logs a warning, so values silently stop surviving reloads.
Event traps
Key modifiers are kebab-cased
KeyboardEvent.keynames (.enter,.escape,.arrow-down,.page-down,.slash). Numeric key codes (@keyup.65) do nothing in v3.<!-- driftwatch-ignore-next-line path/missing -->Note from source code (
utils/on.js,keyToModifiers): a modifier matches when it equals the kebab-casede.key, or one of these aliases:ctrl,cmd,slash,space,spacebar,esc,up,down,left,right,period,comma,equal,minus,underscore. A number never equalse.key, so.65never fires.- Attribute event names are lowercase.
@myEventlistens formyevent. Dispatch kebab-case names, or listen with.camel(@my-event.camelhearsmyEvent);.dotmaps@my-event.dottomy.event. $dispatchbubbles up, never sideways. A sibling or separate component hears it only through@name.window..outsideonly runs while its element is visible, so it pairs withx-showon that same element; on an element that is always visible it fires on every click elsewhere.x-model.enterdoes not stop a form from submitting. Add@submit.preventon the form.
References
Read the file whose branch the task is on; each is complete for its topic.
references/directives.md: every core directive with its full modifier set (x-onkeys and mouse modifiers,x-modelsync modifiers,x-transitionphases,x-bindobject form,x-modelable,x-teleportevent forwarding). When a directive's exact syntax or modifier is in question.references/magics.md:$el,$root,$refs,$event,$store,$watch,$dispatch(cancelable, options, intox-model),$nextTick,$data,$id. When using a magic beyond the obvious.references/globals.md:Alpine.data(arguments,init,destroy),Alpine.store,Alpine.bind, bundle registration,alpine:init/alpine:initialized. When registering anything.references/plugins.md: install and full API of the nine official plugins. When a task touches positioning, collapse, focus trapping, visibility, input masks, morphing server HTML, persistence, resize or drag sorting.references/patterns.md: composition, communication between components, fetch and form state, lazy work, server-rendered pages (Livewire, htmx, Turbo), testing and debugging. When building something beyond one self-contained component.references/extending.md:Alpine.directive,Alpine.magic, plugin packaging,Alpine.reactive/Alpine.effect, async expressions,Alpine.deferInit/Alpine.interceptInit. When writing a custom directive, magic or plugin, or integrating async-loaded components.references/csp.md: the CSP build and its expression subset. When the page's policy has no'unsafe-eval'.
Official docs: https://alpinejs.dev
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

