alpinejs.nvim icon

alpinejs.nvim

Alpine.js Highlighting and Completion for Neovim

alpinejs.nvim is a free, open source Neovim plugin that brings Alpine.js developer support to Neovim: directive and magic-property highlighting, optional nvim-cmp completion, and VS Code-format snippets. It mirrors the language coverage of Alpine.js Tools, the VS Code extension this plugin is based on: HTML, EJS, PHP, Twig, Nunjucks, Blade, Liquid, and Jinja2.

All dependencies beyond Neovim itself are optional. The plugin degrades gracefully when nvim-treesitter, nvim-cmp, or LuaSnip are absent.

Features

  • Directive and magic-property highlighting

    Highlighting for Alpine directives (x-data, x-on:click, :class, @click), shorthand forms (:attr, @event), .modifier.chains, and the inline JS expressions inside directive values, including magic properties ($el, $refs, $store, ...).

  • Two highlighting paths, chosen automatically per buffer

    Tree-sitter (primary): query additions in queries/html/ and queries/javascript/ layer onto whatever html/javascript parse tree is already active — including .ejs buffers whose HTML content is injected via ejs.nvim's embedded_template -> html injection, with no .ejs-specific code. Legacy :syntax regex (fallback): used on buffers where Tree-sitter highlighting isn't active, so users without nvim-treesitter still get highlighting.

  • Configurable filetype list

    Defaults to the same 8 languages Alpine.js Tools supports (HTML, EJS, PHP, Twig, Nunjucks, Blade, Liquid, Jinja2), mapped to 9 filetype strings, correctable via setup({ filetypes = {...} }).

  • nvim-cmp completion

    Optional source completing directive names and magic properties in HTML attribute position. Loads only if nvim-cmp is installed.

  • Snippets

    Shipped in the same package.json + snippets/*.json layout as rafamadriz/friendly-snippets, loaded by LuaSnip's own VS Code loader, with no LuaSnip dependency in this plugin's code at all.

  • Health checks

    :checkhealth alpinejs (or :AlpineHealth) diagnoses your setup.

Prerequisites

All dependencies are optional. The plugin degrades gracefully when any of them are absent.

Dependency Purpose Required
Neovim >= 0.9vim.treesitter.query.add_directive()Yes
nvim-treesitter/nvim-treesitterhtml/javascript parsers, for the primary highlighting pathRecommended
hrsh7th/nvim-cmpDirective/magic-property completionOptional
L3MON4D3/LuaSnipSnippet engineOptional

Installation

lazy.nvim

{
  "connorontheweb/alpinejs.nvim",
  ft = { "html", "ejs", "php", "twig", "blade", "liquid", "jinja", "htmldjango", "nunjucks" },
  dependencies = {
    "nvim-treesitter/nvim-treesitter", -- optional, recommended
    "hrsh7th/nvim-cmp",                -- optional
    "L3MON4D3/LuaSnip",                -- optional
  },
  opts = {},
}

With opts = {}, lazy.nvim calls setup() for you. If the default filetype list already matches your setup, you can omit opts entirely — plugin/alpinejs.lua bootstraps setup() with defaults the first time any default filetype is opened.

No plugin manager (built-in packages)

mkdir -p ~/.local/share/nvim/site/pack/plugins/start
git clone https://github.com/connorontheweb/alpinejs.nvim \
  ~/.local/share/nvim/site/pack/plugins/start/alpinejs.nvim

vim-plug

Plug 'connorontheweb/alpinejs.nvim'

packer.nvim

use 'connorontheweb/alpinejs.nvim'

mini.deps

MiniDeps.add('connorontheweb/alpinejs.nvim')
require('alpinejs').setup()

Post-install: install the Tree-sitter parsers (recommended)

:TSInstall html javascript

Without these, the plugin falls back to the legacy :syntax regex highlighter automatically, so nothing needs to be configured differently either way.

Configuration

All options default as shown. Pass overrides to require('alpinejs').setup(), or via opts if using lazy.nvim:

require('alpinejs').setup({
  -- buffers to activate Alpine support on; see config.lua for the full
  -- default list and the language -> filetype-string mapping it assumes
  filetypes = { 'html', 'ejs', 'php', 'twig', 'blade', 'liquid', 'jinja', 'htmldjango', 'nunjucks' },
  highlight = true, -- enable the :syntax regex fallback path
  cmp       = true, -- register the nvim-cmp source (skipped if nvim-cmp absent)
})

Health Checks

Run :checkhealth alpinejs (or :AlpineHealth) to diagnose your setup: Neovim version, active filetype list, whether the html Tree-sitter parser is available, whether nvim-cmp/LuaSnip are installed, and, for every configured filetype other than HTML, whether highlighting will actually activate for it.

How It Works

The Tree-sitter path is purely declarative: query files Neovim's loader merges across the whole runtimepath whenever a buffer already has Tree-sitter highlighting active for html or javascript. Every styled capture (@alpinejs.directive, @alpinejs.modifier, @alpinejs.shorthand, @alpinejs.magic) sets an explicit priority of 110 via (#set! @capture "priority" 110), and the unstyled anchor captures set 90. This isn't decorative: when multiple captures tie on Tree-sitter's default priority of 100 at the exact same range, Neovim renders whichever capture comes last in the merged query text — an order that depends on runtimepath plugin load order and can't be controlled or predicted. Without the explicit priorities, unstyled anchor captures could end up ordered after the styled ones and silently blank them out instead of layering underneath, which is exactly what happened before the 1.0.1 fix.

Because .ejs buffers parsed via ejs.nvim's embedded_template parser inject a genuine, separate html language tree for their HTML content, these same query files apply there too automatically, with no .ejs-specific code anywhere in this plugin.

The legacy :syntax fallback layers :syntax match/region rules into the existing htmlTag region via containedin=, reusing the real syntax/javascript.vim grammar for directive values via :syntax include. It's only applied to a buffer if Tree-sitter highlighting isn't (and doesn't become) active for that buffer. containedin=htmlTag is a group-name hook, not a filetype-specific one, so it activates for any filetype whose syntax happens to define htmlTag, with no per-language code in this plugin — verified directly for PHP, htmldjango, and liquid.

Related