できない.dev

PostCSS で CSS のネストが展開できない

ネストの展開にはプラグインが要る。
仕様準拠の postcss-nesting と Sass 風の postcss-nested は別物で、どちらを入れたかによって書ける記法が変わる。

公開: 更新:

症状できない
npx postcss src/app.css
…
  a {
    color: #10b981;
  }
}

2026-10-22 に node:20(PostCSS 8.5.31 / Node.js 20.20.2) で実際に打って取った出力。検証環境の詳細

要約

PostCSS 本体はネストを展開しない。
ネストした CSS をそのままビルドすると、対応していない環境では中のルールが丸ごと無視される。
展開するにはプラグインを入れる。

選択肢は 2 つあり、書ける記法が違う。

# CSS Nesting 仕様に沿う
npm install -D postcss-nesting
 
# Sass と同じ感覚で書く
npm install -D postcss-nested

実行例

プラグインを登録しない設定ではネストが書いたまま出力され、postcss-nesting 13 を登録すると .card:hover や .card a のように平坦なセレクタへ展開される。
要素セレクタの前に & を付けた書き方も同じように展開される。

$ npx postcss src/app.css
.card {
  color: #eee;
 
  &:hover {
    color: #fff;
  }
 
  > .title {
    font-weight: 700;
  }
 
  a {
    color: #10b981;
  }
}
$ npx postcss src/app.css
.card {
  color: #eee;
}
.card:hover {
    color: #fff;
  }
.card  > .title {
    font-weight: 700;
  }
.card a {
    color: #10b981;
  }
$ npx postcss src/app.css
.card {
  color: #eee;
}
.card a {
    color: #10b981;
  }

— 2026-10-22 時点の出力

検証環境Node.js 20.20.2 / npm 10.8.2 / Python 3.11.2 / 2026-10-22 検証(ほか 2 件)
検証日
実行環境
node:20 Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5
  • PostCSS 8.5.31

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

解決策

1. どちらのプラグインを使うか決める

仕様に沿った書き方をしたいなら postcss-nesting を使う。

// postcss.config.js
export default {
  plugins: {
    "postcss-import": {},
    "postcss-nesting": {},
    autoprefixer: {},
  },
};

Sass からの移行で、既存の記法をそのまま活かしたいなら postcss-nested を選ぶ。
両方を同時に入れる構成は避けること。

2. postcss-nesting の版で & の要否が変わる

postcss-nesting は仕様どおりの解釈をする。&:hover のように親を参照する場合と、> .bar のように結合子で始まる場合はそのまま書ける。

.card {
  color: #eee;
 
  &:hover {
    color: #fff;
  }
 
  > .title {
    font-weight: 700;
  }
}

要素セレクタで始まるネスト(a { ... })は、仕様の改訂に合わせて postcss-nesting 11.3.0(2023 年 6 月)から & なしでも展開されるようになった。
それより前の版ではこのルールは展開されず、11.2.1〜11.2.2 では「Nested selectors must start with a symbol and "a" begins with a letter.」という警告が出る。
古い版を使っている場合は & を付けるか、プラグインを更新する。

.card {
  /* 11.3.0 以降は .card a に展開される */
  a {
    color: #10b981;
  }
 
  /* 11.3.0 より前の版ではこちらの書き方が必要 */
  & a {
    color: #10b981;
  }
}

3. import より後ろに置く

postcss-import は必ず先頭だ。
ネスト系はその後ろに置く。

export default {
  plugins: {
    "postcss-import": {},
    "postcss-nesting": {},
  },
};

順序の考え方は /postcss/import-not-inlined/ にまとめてある。

4. 出力を見て確認する

展開できていれば、出力側にネストは残らない。

npx postcss src/app.css -o dist/app.css
grep -n "&" dist/app.css

& が残っていれば、そのファイルは処理されていない。
設定ファイルが読まれていない可能性もあるので、/postcss/config-not-loaded-esm/ も確認する。

なお Tailwind CSS を使っている場合、ネストの扱いはフレームワーク側の設定に含まれていることがある。
自分の postcss.config.js に足す前に、既に有効になっていないかを確かめる。

よくある原因

  1. プラグインが無い: PostCSS は CSS を解析する土台で、記法の変換はプラグインの担当。
    ネスト用のプラグインを登録しなければ、ネストは書いたまま出力される。
  2. 仕様準拠のプラグインに Sass 風を書いている: postcss-nesting は W3C の CSS Nesting 仕様の実装で、公式リポジトリも「If you want nested rules the same way Sass works you might want to use PostCSS Nested instead.」と案内している。
    Sass のセレクタ結合(&__element のような書き方)はこちらでは扱えない。
  3. プラグインの順序: @import で読み込んだファイルのネストを展開するには、postcss-import が先に展開を終えている必要がある。
    順序が逆だと、読み込み先のネストだけが残る。
  4. そもそも変換が要らない: ネイティブの CSS ネストに対応した環境だけを対象にしているなら、変換なしでも動く。
    この場合は対象ブラウザの確認が先。