Web Components Language Server - Neovim
First-class Neovim support for the Web Components Language Server. The plugin wires the server into Neovim’s built-in LSP client so you get completions, hovers, and diagnostics for any project that ships a custom-elements.json file.
Highlights
- 🔌 Zero-config attach for HTML, Astro, Vue, Svelte, JSX/TSX, and Markdown files
- 🧠 Tag, attribute, and attribute-value completions sourced from your Custom Elements Manifest
- 🛡️ Diagnostics for unknown tags, invalid attribute values, duplicates, and deprecated APIs
- ♻️ Automatic restart when
wc.config.*,custom-elements.json,package.json, ornode_moduleschange - 🗂️ Commands to restart/stop/start the server plus an API (
require("wc_language_server").restart())
Prerequisites
- Neovim 0.9 or newer (0.10+ recommended)
- A project that exposes a
custom-elements.json(directly or via dependencies) - Language Server Executable: Either download manually from GitHub releases or install via mason.nvim
Installation
Download Language Server Executable
- Go to the GitHub Releases page.
- Download the executable matching your OS and architecture:
wc-language-server.js- Single-file JavaScript bundle (requires Node.js)wc-language-server-linux-x64- Linux x64 executablewc-language-server-linux-arm64- Linux ARM64 executablewc-language-server-macos-x64- macOS x64 executablewc-language-server-macos-arm64- macOS ARM64 executablewc-language-server-windows-x64.exe- Windows x64 executable
- Place the downloaded file in
packages/neovim/server/bin/and make it executable (e.g.,chmod +x wc-language-server-linux-x64on Unix systems).
Mason (Recommended)
If you have mason.nvim installed, you can install the language server executable automatically:
:MasonInstall wc-language-serverThis will download and install the appropriate executable for your platform. The Neovim plugin will automatically detect and use the mason-installed executable.
Install Neovim Plugin
The Neovim plugin requires the language server executable to be installed as described above (either manually or via mason).
lazy.nvim
{ "wc-toolkit/wc-language-server", dir = "/path/to/wc-language-server", ft = { "html", "javascriptreact", "typescriptreact", "astro", "svelte", "vue", "markdown", "mdx" }, config = function() require("wc_language_server").setup() end,}packer.nvim
use({ "wc-toolkit/wc-language-server", as = "wc-language-server", config = function() require("wc_language_server").setup() end,})ℹ️ If you clone to a different path, make sure Neovim’s
runtimepathincludespackages/neovim.
Quick Start
require("wc_language_server").setup({ autostart = true, filetypes = { "html", "astro", "vue" }, root_dir_patterns = { "wc.config.js", "package.json", ".git" }, tsdk = vim.fn.getcwd() .. "/node_modules/typescript/lib",})- Open an HTML (or supported) file.
- Trigger completion with
<C-x><C-o>(or your completion plugin) to see<sl-…>tags. - Hover with
Kto see docs + diagnostics. - Use
:WcLanguageServerRestartif you editwc.config.*or add libraries.
Hover popups are rendered as Markdown automatically (bold, code fences, tables, etc.). Set hover.markdown_highlighting = false if you want the legacy plain-text view.
Project Configuration (wc.config.js)
The language server reads settings from wc.config.js (or .ts/.mjs/.cjs) at the project root. Use it to point the server at the right manifest, scope files, and override diagnostics.
export default { /** Fetch a manifest from a custom path or URL */ manifestSrc: "./dist/custom-elements.json", /** Narrow which files opt into the language server */ include: ["src/**/*.ts", "src/**/*.html"], /** Optional: skip specific globs */ exclude: ["**/*.stories.ts"], /** Per-library overrides */ libraries: { "@your/pkg": { manifestSrc: "https://cdn.example.com/custom-elements.json", /** Adjust tag names before validation */ tagFormatter: (tag) => tag.replace(/^x-/, "my-"), }, }, /** Silence/relax certain diagnostics */ diagnosticSeverity: { duplicateAttribute: "warning", unknownElement: "info", },};Every time you change wc.config.* the Neovim plugin automatically restarts the server, so edits apply on your next hover/completion.
Plugin Configuration
| Option | Type | Default | Description |
|---|---|---|---|
autostart | boolean | true | Attach automatically when a matching filetype opens. |
filetypes | string[] | HTML + popular templating filetypes | Customize the attach list. |
root_dir_patterns | string[] | { "wc.config.js", "package.json", ".git" } | Upward search markers for workspace detection. |
cmd | string[] or fun(root) | auto-detected | Override the server command. Defaults to the bundled binary, then wc-language-server on $PATH. |
tsdk | string | auto-detected | Path to node_modules/typescript/lib. If omitted a best-effort search runs. |
watch_patterns/watch_files | string[] | important config names | Control which files trigger automatic restarts. |
debounce_ms | number | 350 | Debounce before restarting after file system events. |
settings | table | {} | Passed to the language server via initializationOptions. |
capabilities | table | nil | Override reported LSP capabilities. |
on_attach | function | nil | Called after the client attaches. Use this for formatting, keymaps, etc. |
diagnostics | table/false | see defaults | Configure vim.diagnostic (virtual text is off by default). |
hover | table/false | { keymap = "K", include_diagnostics = true, markdown_highlighting = true } | Customize the combined hover/diagnostic popup, keybinding, and whether markdown syntax/Tree-sitter highlighting is applied to the floating window. |
completion.set_omnifunc | boolean | true | Set omnifunc = v:lua.vim.lsp.omnifunc so <C-x><C-o> uses the language server. |
Commands & Keybinds
| Command | Description |
|---|---|
:WcLanguageServerStart | Attach the client for the current buffer. |
:WcLanguageServerStop | Stop the client for the current project root. |
:WcLanguageServerRestart | Restart the client and reload manifests. |
K(configurable) shows hover text plus any diagnostics on the current line.<C-x><C-o>triggers LSP completion; use your completion framework (e.g., nvim-cmp) if preferred.
Troubleshooting
- No completions or hover – run
:LspInfoto ensurewc-language-serveris attached. If not, verify the filetype is listed infiletypesand that the root dir was detected correctly. - “Unable to locate wc-language-server binary” – build the server with
pnpm build:lsor setcmdto a global installation. - Diagnostics missing – check
:lua vim.diagnostic.open_float()at the cursor; if nothing shows, ensurecustom-elements.jsonexists and watch for errors in:messages. - Watching doesn’t restart – confirm the files you edit match
watch_patternsor add your own (e.g., for monorepo manifests). - Too many completion entries – limit completion sources (buffer/snippet) or use
completion.set_omnifunc = falseand delegate to your completion plugin for filtering.
Enjoy enhanced Web Components development! 🎉