@flarian/language-plugin-css-modules

Vue language tools plugin: types for external CSS modules straight from PostCSS, without .d.ts files
Install
npm install @flarian/language-plugin-css-modules
Latest Version:1.0.0
Vue:3
License:MIT
Last Updated:Sep 13, 2026
Links: GitHub  ·  npm
Maintainer: r.musin

@flarian/language-plugin-css-modules

Types for external CSS modules in Vue single-file components, computed by the Vue language tools themselves. No .d.ts files on disk, no build step, no watcher.

<style module="styles" src="./styles.module.pcss" lang="pcss" />

With the plugin, styles.hint is checked against the real class names of styles.module.pcss — in the editor and in vue-tsc alike. A typo is an error, and go-to-definition jumps into the stylesheet.


Why

Volar reads class names only from the inline content of a <style module> block. For src="…" it emits typeof import('./styles.module.pcss').default and leaves the rest to TypeScript module resolution.

Vite declares module '*.module.pcss' with an index signature. So that import resolves to "any string key", and every typo passes silently.

The common workaround writes a .d.ts next to each stylesheet. This plugin instead turns every *.module.pcss into a virtual TypeScript module inside the language service.

Class names are taken after PostCSS runs, so classes born from transformations are typed too:

.hint-test {
	&-deep {
		color: blue;
	}
}
styles.hintTest; // ok
styles.hintTestDeep; // ok

Install

pnpm add -D @flarian/language-plugin-css-modules

postcss is a peer dependency and is loaded from your project.


Setup

tsconfig.json:

{
	"vueCompilerOptions": {
		"extensions": [".vue", ".pcss"],
		"strictCssModules": true,
		"resolveStyleImports": true,
		"plugins": [
			{
				"name": "@flarian/language-plugin-css-modules",
				"localsConvention": "camelCaseOnly",
			},
		],
	},
}

postcss.config.cjs, shared with Vite:

module.exports = {
	plugins: {
		'postcss-nested': {},
	},
};

Plugins are loaded by name from your project, so the config needs no require, and @typescript-eslint/no-require-imports has nothing to complain about. An array of plugin instances works as well.

Vite picks this file up on its own, and the plugin reads the same file. Types and the build use one list of PostCSS plugins, so they cannot drift apart.


Options

Option Default Meaning
localsConvention names as they are Same values as css.modules.localsConvention in Vite: camelCase, camelCaseOnly, dashes, dashesOnly. Must match the build, or types and runtime disagree.
moduleSuffix .module.pcss Which stylesheets are CSS modules.
extensions [".pcss"] Stylesheet extensions the plugin owns. Every entry must also be listed in vueCompilerOptions.extensions.
postcssConfig postcss.config.cjs Config file name, searched upward from the stylesheet.

Name conversion mirrors postcss-modules exactly, including lodash.camelcase for the camelCase modes.


Limitations

  • Only synchronous PostCSS plugins affect types, because the language service is synchronous. With an asynchronous plugin the stylesheet is parsed without plugins.
  • A function as localsConvention cannot be written in tsconfig.json.
  • :global(...) classes are not module keys, same as in postcss-modules.

How it works

The plugin registers two instances.

The first one runs before the built-in ones (order: -1) and claims every stylesheet extension. Without it the built-in .vue handler takes the file, parses CSS as an SFC, and the import types as an empty component.

The second one runs after the built-in code generation (order: 1) and replaces the generated script with a module that declares the class names. Each key is mapped back to its class in the stylesheet, so navigation lands on the right line.

Related Packages

@seepine/vue-tsc

vue-tsc 的 Node 入口:在标准 tsc 之上注入 Volar Vue 语言插件,使命令行与编辑器都...

64 0
vue-css-modules

Seamless mapping of class names to CSS modules inside of Vue components.

3,724 37
vite-plugin-dts

vite-plugin-dts

147,380,553 1,531
vue-go-tsc

Blazingly fast Vue 3 type checker powered by typescript-go — a Go-native alterna...

25,656 0

Version History

Version Released Vue License
1.0.0 3.5.42 MIT