できない.dev

Vite で CSS Modules のクラスが適用されない

Vite は *.module.css / *.module.scss のファイル名を CSS Modules として自動処理するが、命名が外れている、import から styles.foo を経由していない、css.modules.localsConvention のずれでケバブケース名が取れない、などで適用されないことがある。

公開: 更新:

実行例あり(2026-09-18 に実環境で検証)

要約

Vite で CSS Modules が効かない場合、ほぼ確実に ファイル名が *.module.css パターンになっていない か、JS 側で styles.foo を経由していない のどちらか。vite.config.ts で挙動を変えるのは命名規則の調整が必要な時のみで、まず命名と import 方法を確認する。

実行例

拡張子を .module.css に変えると出力 CSS のクラス名はハッシュ付きに変わるが、マークアップ側で生の button を当てたままだと両者が食い違う。styles.button 経由にすると一致し、localsConvention を入れるまでは styles.fooBar が undefined のままであることも確認できる。

$ npx vite build
vite v6.4.3 building for production...
transforming...
✓ 2 modules transformed.
rendering chunks...
computing gzip size...
dist/main.css  0.05 kB │ gzip: 0.06 kB
dist/main.js   0.07 kB │ gzip: 0.09 kB
✓ built in 28ms
$ node dist/main.js
マークアップに付けるクラス: button
$ cat dist/main.css
.button { color: red; }
.foo-bar { color: blue; }
$ npx vite build
vite v6.4.3 building for production...
transforming...
✓ 2 modules transformed.
rendering chunks...
computing gzip size...
dist/main.css  0.07 kB │ gzip: 0.07 kB
dist/main.js   0.21 kB │ gzip: 0.18 kB
✓ built in 48ms
$ node dist/main.js
クラス名の対応表: { button: '_button_1es0y_1', 'foo-bar': '_foo-bar_1es0y_2' }
マークアップに付けるクラス: button
$ cat dist/main.css
._button_1es0y_1 { color: red; }
._foo-bar_1es0y_2 { color: blue; }
$ npx vite build
vite v6.4.3 building for production...
transforming...
✓ 2 modules transformed.
rendering chunks...
computing gzip size...
dist/main.css  0.07 kB │ gzip: 0.07 kB
dist/main.js   0.26 kB │ gzip: 0.19 kB
✓ built in 48ms
$ node dist/main.js
クラス名の対応表: { button: '_button_1es0y_1', 'foo-bar': '_foo-bar_1es0y_2' }
マークアップに付けるクラス: _button_1es0y_1
styles.fooBar: undefined
$ npx vite build
vite v6.4.3 building for production...
transforming...
✓ 2 modules transformed.
rendering chunks...
computing gzip size...
dist/main.css  0.07 kB │ gzip: 0.07 kB
dist/main.js   0.28 kB │ gzip: 0.19 kB
✓ built in 45ms
$ node dist/main.js
クラス名の対応表: { button: '_button_1es0y_1', fooBar: '_foo-bar_1es0y_2' }
マークアップに付けるクラス: _button_1es0y_1
styles.fooBar: _foo-bar_1es0y_2

— 2026-09-18 時点の出力

検証環境

検証日
実行環境
node:20Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. 拡張子ミス: Button.css のままだと通常 CSS として全コンポーネントにグローバル適用される
  2. import 経由していない: import './Button.module.css' のような副作用 import だけだとクラス名はハッシュ化されているので、そのまま書いたクラス名にはマッチしない
  3. camelCase 期待: .foo-bar クラスを styles.fooBar で取りたいのに localsConvention が camelCase 系になっていない
  4. PostCSS の順序: 他のプラグインが先に走ると CSS Modules のハッシュ化結果が壊れる場合がある

解決策

1. 命名を .module.css にする

src/components/Button.module.css   ← OK
src/components/Button.css          ← 通常 CSS

Vite の Features ガイド(新しいタブで開く) のとおり、*.module.{css,scss,sass,less,styl,stylus,pcss,postcss,sss} がデフォルトの判定パターン。

2. JS 側で styles.xxx を経由する

import styles from "./Button.module.css";
 
export function Button() {
  return <button className={styles.button}>OK</button>;
}

副作用 import だけだと CSS は読み込まれるが、クラス名はビルド時にハッシュ化されているため <button className="button"> と書いても当たらない。

3. ケバブケース → camelCase 変換

Button.module.css で .foo-bar と定義したクラスを JS から styles.fooBar で取りたい場合:

// vite.config.ts
import { defineConfig } from "vite";
 
export default defineConfig({
  css: {
    modules: {
      localsConvention: "camelCaseOnly",
    },
  },
});

camelCaseOnly は元のケバブケース名を消すため、両方欲しい場合は camelCase を使う。
詳細は Shared Options のリファレンス(新しいタブで開く) の css.modules を参照。

4. PostCSS との競合

postcss.config.js で postcss-import などを使っている場合は CSS Modules 処理の前に解決させる。

module.exports = {
  plugins: [
    require("postcss-import"),
    require("autoprefixer"),
  ],
};

postcss-import を plugins 配列の先頭に置くと、@import 解決後に Vite が CSS Modules 化する。

この記事は役立ちましたか?