Alpine.js Tools
IntelliSense and Syntax Highlighting for VS Code
Alpine.js Tools is a free, open source Visual Studio Code extension that delivers complete Alpine.js 3 tooling across twelve languages — nine markup and template languages plus JSX and TSX. The most widely installed Alpine extension has been unmaintained since 2020 and covers only Alpine v2 with no JavaScript highlighting inside directive values. Alpine.js Tools was built from scratch to address that gap.
The extension activates for html, ejs, php, twig, nunjucks, blade, liquid, jinja-html, astro, javascript, javascriptreact, and typescriptreact files. Jinja2 templates that keep the plain .html extension (the common Flask/Django case) are already covered by the html support. It requires no dependencies and no configuration, and works with any Alpine.js v3 project.
Features
-
JavaScript syntax highlighting inside Alpine directives
Attribute values on
x-*,:, and@attributes are tokenized as full JavaScript. Operators, strings, arrow functions, ternaries, and method calls all receive correct colors from the active theme. A single injection grammar targets the host scope for every supported markup language:text.html.basic,text.html.derivative,text.html.ejs,text.html.php,text.html.twig,text.html.nunjucks,text.html.php.blade,text.html.liquid,source.liquid,text.html.jinja, andsource.astro, with no per-language duplication. Every one of those scope names is verified by tokenizing a real Alpine sample against the grammar each companion extension actually installs, rather than assumed from the language id. A second injection grammar covers JSX and TSX, scoped tometa.taginsidesource.tsx,source.js.jsx, andsource.jsso it is structurally unable to reach non-JSX code. Template-delimiter output ({{ ... }},{% ... %}) inside a directive value is passed through untouched rather than mis-tokenized as JavaScript. -
Alpine.js in Astro
.astrofiles get the full feature set, the same as every other markup language, including the@clickand:classshorthands — Astro passes attributes straight through to HTML rather than claiming those names for itself the way Vue does. Astro's own namespaced attributes are left alone:client:load,transition:animate, andset:htmlare never read as Alpine's:shorthand..astrofiles are also swept forAlpine.data()andAlpine.store()registrations, so a store registered in a component's frontmatter turns up in$store.completions. Requires the official Astro extension for the.astrolanguage itself. -
Front matter is never scanned for directives
A
---fenced block at the top of a file is front matter in every ecosystem that uses one, and it is never markup. Astro's frontmatter is TypeScript — the only JavaScript region in a template language that no tag delimits — so without this,const diff = x-y > 0in your frontmatter would be reported as an unknown directive. The rule is keyed on the fence rather than on the language, so YAML front matter in Jekyll, Hugo, and Eleventy templates is skipped by the same code. The closing fence must be a line containing exactly---, so a stray---cannot blank the rest of a file. -
Alpine.js in JSX and TSX
Alpine directives are fully supported inside JSX in
.jsx,.tsx, and.jsfiles, for server-rendered JSX setups such as KitaJS and Hono. Nothing is framework-specific: the gating is on JSX syntax itself, so Preact and React SSR, Solid, and anything else rendering Alpine attributes from JSX are covered by the same code. Hover documentation, magic property and modifier completions,x-dataproperty completions, diagnostics with Quick Fixes, Go to Definition, directive-name IntelliSense, snippets, and JavaScript highlighting inside directive values all work there. Use the long forms —x-on:clickandx-bind:classrather than@clickand:class, which are not valid JSX attribute names. -
JSX shorthand diagnostic
Reaching for
@clickor:classout of habit in a.jsxor.tsxfile is the most likely mistake when bringing Alpine markup to JSX, and TypeScript's own report (TS1003 Identifier expected) says nothing about Alpine. A warning names the problem directly and a Quick Fix rewrites it to the long form. It is only ever raised inside a JSX opening tag, so decorators and object-literal keys are untouched. -
Alpine syntax is only ever an attribute name
Both families scan the document structurally and report only inside an opening tag's attribute region, rather than pattern-matching text that happens to look like a directive. In markup that means
x-axisin a sentence,x-yin a<script>block, and a typo inside<!-- ... -->are never flagged, while Blade's@if($cond), Twig's{{ attrs }}, Liquid's{% if %}, EJS's<%= attrs %>, and PHP's<?php ... ?>inside a tag are stepped over without losing the real Alpine attribute beside them. In JSX the scanner skips comments and string and template literals and accepts a<as a tag only when an element name and a well-formed attribute region follow, sonew Map<string, number>()andi.n < 5 && i.n > 1are rejected,const diff = x-y > 0raises no diagnostic,{ 'color':theme.primary }raises nox-bindhover,@Injectable()raises nox-onhover, and a bare$offers no magic properties. A React project that never uses Alpine sees nothing from the extension. -
Hover documentation for all directives and magic properties
Hover over any Alpine directive, shorthand, or magic property to see a description and a direct link to the official Alpine.js documentation. All 18 core directives are covered:
x-data,x-init,x-show,x-bind,x-on,x-text,x-html,x-model,x-modelable,x-for,x-transition,x-effect,x-ignore,x-ref,x-cloak,x-teleport,x-if, andx-id— alongside 6 plugin directives and the 6 class-based transition attributes (x-transition:enter,:enter-start,:enter-end,x-transition:leave,:leave-start,:leave-end), the standard way to drive Alpine transitions with Tailwind classes. Shorthand forms are handled: hovering@clicksurfacesx-ondocs with a note that it is shorthand forx-on:click. Dot-modifiers are also handled: hoveringx-model.numbershowsx-modeldocs. All 11 magic properties are covered:$el,$refs,$store,$watch,$dispatch,$nextTick,$root,$data,$id,$persist, and$event. -
Magic property completions
Typing
$inside any Alpine expression triggers completions for all magic properties with type signatures, descriptions, and tab-stop-aware insert text. After$refs., completions list everyx-refname declared in the current file. After$store., completions list everyAlpine.store('name', ...)registration found across workspace JavaScript, JSX, TypeScript, TSX, HTML, Astro, Liquid, and Jinja files, backed by a file-system watcher. -
Modifier completions
Typing
.after an Alpine directive name surfaces the valid modifiers for that directive. Event directives offerprevent,stop,self,outside,window,document,once,passive,debounce,throttle,camel,dot, and key names.x-modelofferslazy,number,boolean, andtrim.x-transitionoffersenter,leave,opacity,scale, andorigin-*. Already-applied modifiers in a chain are filtered out automatically. -
Unknown directive diagnostics
Any
x-*attribute that is not a recognized Alpine core or plugin directive is underlined with a Warning. A "did you mean" hint is shown when a close match exists, and a Quick Fix lightbulb action offers a one-click replacement. All 18 core directives and official plugin directives are never flagged. Diagnostics are debounced at 500 ms and cleared as you type. Livewire'swire:model, Blade's@foreach, Astro'sclient:load, and Tailwind classes such astranslate-x-1/2andhover:text-red-500are never mistaken for Alpine syntax. -
Go to Definition for Alpine components
Press F12 (or Ctrl+Click) anywhere inside
x-data="componentName"to jump directly to theAlpine.data('componentName', ...)registration in workspace JavaScript, JSX, TypeScript, TSX, HTML, Astro, Liquid, or Jinja files. Multiple registration sites are all shown. Inline object literals are intentionally skipped. -
Plugin directive completions
x-intersect,x-collapse,x-mask,x-sort,x-anchor, andx-trapappear in VS Code's HTML attribute IntelliSense alongside core directives, with hover descriptions and links to each plugin's documentation. -
Directive value completions
Inside
x-data="...", completions suggestAlpine.data('name', ...)component names from the workspace. Inside any other Alpine directive value, completions suggest the reactive property names extracted from the nearestx-dataobject literal in the current file. -
Snippets
42 snippets available across HTML, EJS, PHP, Twig, Nunjucks, Blade, Liquid, Jinja-HTML, Astro, JavaScript, and JSX/TSX: all 18 directive attributes,
x-transition-classesscaffolding the full six-phase class-based transition set,template-forandtemplate-iffull block wrappers,Alpine.data()andAlpine.store()scaffolds, magic property snippets, modifier snippets, and all official plugin directives including their variants. In Astro, JSX, and JavaScript files they are context-gated rather than offered everywhere: attribute snippets only inside a tag, scaffolds only outside one, and nothing at all inside Astro's frontmatter.
Supported File Types
html • ejs • php • twig • nunjucks • blade • liquid • jinja-html • astro • javascript • javascriptreact • typescriptreact
Liquid support targets files opened with the liquid language id, covering all three common Liquid extensions (Shopify Liquid, Liquid, and Shopify Theme Check). Jinja2 support targets the jinja-html language id (used by the Jinja HTML extension); Jinja2 files kept as plain html are already covered by the extension's HTML support. Astro support targets the astro language id contributed by the official Astro extension. The JavaScript injection grammar targets each language's host scope, so highlighting works correctly in every file type without separate grammar files per language.
JSX is covered in .jsx and .tsx files and in plain .js files, since JSX in a .js file gets the javascript language id rather than javascriptreact. Every JSX-facing provider is gated on being structurally inside a JSX opening tag, so ordinary JavaScript and TypeScript are untouched.
Compatibility with EJS Colorizer
Alpine.js Tools is designed to compose cleanly with EJS Colorizer. The injection grammar targets text.html.ejs by scope name, which EJS Colorizer registers as a named, stable host scope. Both extensions can be active simultaneously with no tokenization conflicts. Alpine directive values inside .ejs files receive full JavaScript highlighting from Alpine.js Tools on top of the EJS structure provided by EJS Colorizer.
Settings
The extension is zero-config: every setting defaults to the behavior you get without touching settings.json. These are escape hatches, not setup.
| Setting | Default | Purpose |
|---|---|---|
alpinejsTools.diagnostics.unknownDirective.severity | warning | Turn the heuristic diagnostic down or off |
alpinejsTools.diagnostics.jsxShorthand.severity | warning | The JSX shorthand diagnostic, separately |
alpinejsTools.extraDirectives | [] | Third-party plugin directives |
alpinejsTools.workspaceScan.exclude | [] | Keep build output out of the workspace scan |
Severities are enums rather than on/off switches, because the useful value is hint: the check keeps running and its Quick Fix stays reachable from the lightbulb, but the entry stays out of the Problems panel. A boolean would force a choice between living with a false positive and losing every true positive with it. error, warning, information, and off are the rest.
The two diagnostics are configured separately because they fail differently. The unknown-directive check is the heuristic one, deciding whether an x-... token is an attribute name at all. The JSX shorthand check makes no such judgement — @click= in a .tsx opening tag is a hard TypeScript syntax error either way, and the extension is only replacing a message that doesn't mention Alpine with one that does. Settings are read per-resource, so a monorepo can turn a check off for one package and leave the rest alone.
alpinejsTools.extraDirectives exists for a different reason than the others. Alpine's core and official plugin directives are a closed list the extension can maintain; third-party plugins register whatever name they like, so a project using one previously got a permanent warning on correct code. Names are accepted with or without the x- prefix and with arguments or modifiers attached — x-clipboard, clipboard, and x-clipboard:copy all register the same base name — and listed names join the "did you mean" candidates too.
Commands
Alpine.js Tools: Rescan Workspace rebuilds the index of Alpine.data() and Alpine.store() registrations and reports how many files were swept and how many components and stores came back. It covers what the file-system watcher cannot see — a branch switched or dependencies installed outside the editor, or a workspace still indexing when the first sweep ran — and it is the way to act on the output-channel message when a scan truncates. A rescan swaps the cache when the sweep completes rather than clearing it first, so completions and go-to-definition keep answering from the previous results throughout.
Known Limitations
x-dataproperty completions use a heuristic (regex) to extract properties from the nearestx-dataobject literal. Complex expressions, computed keys, or spread operators will not be detected.$storename completions requireAlpine.store('name', ...)to appear in a workspace JS, JSX, TS, TSX, HTML, Astro, Liquid, or Jinja file. Stores registered dynamically at runtime will not be listed.- In Astro and JSX, an expression container (
x-data={cart}) holds ordinary TypeScript that the language service already handles, so Alpine completions stay out of it. Usex-data="{ open: false }"for anything you want IntelliSense inside. - In Blade files, the standard Laravel Blade Snippets extension colors Alpine's
@clickand:classattribute names as if they were Blade directives, because its own grammar has a generic@wordrule with no attribute-position check. VS Code always resolves a grammar's own rules ahead of an injected one at the same priority, so this can only be fixed in that extension. - Markup inside tagged template literals (
html`<div x-data="cart">`, as used by hono/html and lit-html) is not recognized. There is no reliable way to distinguish an HTML template from any other string, and guessing from the tag function's name is exactly the kind of heuristic that produces false positives. - In JSX,
x-data={{ open: false }}is deliberately unsupported: Alpine reads the attribute as a string, so an expression container holding a real object renders[object Object]. Usex-data="{ open: false }".
Requirements
- VS Code 1.60.0 or later
- No runtime dependencies
- Works with any Alpine.js v3 project
Installation
Search for Alpine.js Tools in the VS Code Extensions panel, or install directly from the Marketplace. Source code is available on GitHub under the MIT license.