Alpine.js Tools icon

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, and source.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 to meta.tag inside source.tsx, source.js.jsx, and source.js so 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

    .astro files get the full feature set, the same as every other markup language, including the @click and :class shorthands — 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, and set:html are never read as Alpine's : shorthand. .astro files are also swept for Alpine.data() and Alpine.store() registrations, so a store registered in a component's frontmatter turns up in $store. completions. Requires the official Astro extension for the .astro language 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 > 0 in 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 .js files, 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-data property 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:click and x-bind:class rather than @click and :class, which are not valid JSX attribute names.

  • JSX shorthand diagnostic

    Reaching for @click or :class out of habit in a .jsx or .tsx file 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-axis in a sentence, x-y in 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, so new Map<string, number>() and i.n < 5 && i.n > 1 are rejected, const diff = x-y > 0 raises no diagnostic, { 'color':theme.primary } raises no x-bind hover, @Injectable() raises no x-on hover, 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, and x-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 @click surfaces x-on docs with a note that it is shorthand for x-on:click. Dot-modifiers are also handled: hovering x-model.number shows x-model docs. 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 every x-ref name declared in the current file. After $store., completions list every Alpine.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 offer prevent, stop, self, outside, window, document, once, passive, debounce, throttle, camel, dot, and key names. x-model offers lazy, number, boolean, and trim. x-transition offers enter, leave, opacity, scale, and origin-*. 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's wire:model, Blade's @foreach, Astro's client:load, and Tailwind classes such as translate-x-1/2 and hover:text-red-500 are 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 the Alpine.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, and x-trap appear 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 suggest Alpine.data('name', ...) component names from the workspace. Inside any other Alpine directive value, completions suggest the reactive property names extracted from the nearest x-data object 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-classes scaffolding the full six-phase class-based transition set, template-for and template-if full block wrappers, Alpine.data() and Alpine.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

htmlejsphptwignunjucksbladeliquidjinja-htmlastrojavascriptjavascriptreacttypescriptreact

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.

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.

SettingDefaultPurpose
alpinejsTools.diagnostics.unknownDirective.severitywarningTurn the heuristic diagnostic down or off
alpinejsTools.diagnostics.jsxShorthand.severitywarningThe 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-data property completions use a heuristic (regex) to extract properties from the nearest x-data object literal. Complex expressions, computed keys, or spread operators will not be detected.
  • $store name completions require Alpine.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. Use x-data="{ open: false }" for anything you want IntelliSense inside.
  • In Blade files, the standard Laravel Blade Snippets extension colors Alpine's @click and :class attribute names as if they were Blade directives, because its own grammar has a generic @word rule 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]. Use x-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.

Related Extensions