🚕

【Next.js】CSS Modules(SCSS)でのクラスセレクタ

2022/01/15に公開

結論

:global疑似クラスを使うとクラスセレクタに対してスタイルを適用できる。

.menu_ul {
  li {
    a {
      color: #666;
    }
    :global(a.current) {
      color: #ccc;
    }
  }
}

SCSS+CSS Modules

私はフロントエンドは専門外でNext.jsも初心者なのですが、必要があって触っているときに悩む事象がありました。

Next.jsではSass(SCSS)がサポートされているので使いたいと思いました。
セレクタをネスト構造で記述できるのは魅力だと思います。

また、CSS Modulesという仕組みが紹介されていたため、使ってみたいと思いました。
コンポーネント側でCSSの適用対象を決める仕組みで、このHTML要素にこのCSSを適用しよう、ということをコンポーネント側で記述することができます。

import Link from 'next/link'
import styles from '../styles/Sample.module.scss'
const Manu = () => (
  <ul className={styles.menu_ul}>
    <li><Link href="/">Home</Link></li>
    <li><Link href="/xxx">Xxx</Link></li>
    <li><Link href="/yyy">Yyy</Link></li>
  </ul>
)
export default Menu
.menu_ul {
  display: flex;
  justify-content: space-between;
  
  li {
    width: 32%;
    
    a {
      display: inline-block;
      width: 100%;
      background-color: #ddd;
    }
  }
}

上記はメニュー表示のコンポーネントですが、Linkで挿入されるaタグに動的に現在のページを示すクラスを設定し、スタイルを区別しようと思いました。静的な記述では、以下のようになります。

import Link from 'next/link'
import styles from '../styles/Sample.module.scss'
const Manu = () => (
  <ul className={styles.menu_ul}>
    <li><Link href="/"><a className='current'>Home</a></Link></li>
    <li><Link href="/xxx"><a>Xxx</a></Link></li>
    <li><Link href="/yyy"><a>Yyy</a></Link></li>
  </ul>
)
export default Menu

まずLinkに対してclassNameを指定しても効かないというので躓いたりしましたが、中にaタグを書くことでDOM的には<a class="current">Home</a>となりました。
さて、ではスタイルを……ということで以下の記述をしましたが、うまくいきませんでした。

.menu_ul {
  display: flex;
  justify-content: space-between;
  
  li {
    width: 32%;
    
    a {
      display: inline-block;
      width: 100%;
      background-color: #ddd;
    }
    /* 適用されない */
    a.current {
      background-color: #eee;
    }
  }
}

CSS Modulesはクラスセレクタを変換する?

CSS Modulesとしてcss/scssを読み込んだとき、DOMに生成されるクラス名はcss/scssに記述されたものとは異なっています。
例えば.menu_ulを適用したブラウザで確認すると、<ul class="menu_ul_Menu__Xxxxx">のような感じで適切な一意のクラス名に変換されています。
なので、本来のクラスセレクタが使用できないのかもしれません。
この問題はトップレベルで.currentなどを作ってclassName={styles.current}などとすると解決します。もしかしたらネスト構造で書くSCSSと1つ1つの要素にスタイルを適用するCSS Modulesの考え方が食い違っているのかも?とも思います。

:global疑似クラス

何か方法がないか調べた結果、:globalという疑似クラスを使うと良いようです。
:globalは対象のルールをグローバルに適用するものらしいですが、要するに:global(.current)と記述するとクラス名currentの要素すべてに適用されるということで、通常の(Modulesではない)CSS読み込みと同じ動作が期待されるようです。
これを使って以下のように記述することで、無事スタイルが適用されました。

.menu_ul {
  display: flex;
  justify-content: space-between;
  
  li {
    width: 32%;
    
    a {
      display: inline-block;
      width: 100%;
      background-color: #ddd;
    }
    /* OK */
    :global(a.current) {
      background-color: #eee;
    }
  }
}

フロントエンドは言語仕様が複雑で細かい知識が要求されるうえ、こうした問題について検索で引っかかり難くて困ります……。

Discussion