Svelte コンポーネントの <style> ブロック内で :global() 修飾子を使う

Svelte コンポーネントの <style> ブロック内で使用できる :global() 修飾子の使い方について少しハマったのでメモです。

やりたいこと

Svelte コンポーネントでは特定のコンポーネント内の <style> ブロック内で定義した CSS スタイルを他のコンポーネントに適用したり、そのコンポーネントの子孫コンポーネントに適用したりすることができる :global() 修飾子が用意されています。

<style>
	:global(body) {
		/* これは <body> に適用されます */
		margin: 0;
	}

	div :global(strong) {
		/* これは、このコンポーネント内の <div> 要素の中にある任意のコンポーネント内の <strong> 要素に適用されます */
		color: goldenrod;
	}

	p:global(.red) {
		/* これは、このコンポーネントに属し、red クラスを持つすべての <p> 要素に適用されます。(後略)*/
	}
</style>

Svelte components • Docs • Svelte

例えば以下のような焼酎一覧を表示する ShochuList.svelte というコンポーネントがあるとします。

/src/lib/components/ShochuList.svelte
<script>
import Shochu from '$lib/components/Shochu.svelte';

const shochuList = [
  { name: '黒霧島', brewery: '霧島酒造' },
  { name: '明るい農村', brewery: '霧島町蒸留所'},
  /* : */
]
</script>

<table>
  <thead>
    <tr>
      <th>銘柄</th>
      <th>蔵元</th>
    </tr>
  </thead>
  <tbody>
    {#each shochuList as shochu}
      <Shochu {shochu} />
    {/each}
  </tbody>
</table>

このコンポーネントは以下の子コンポーネント Shochu.svelte を参照しています。

/src/lib/components/Shochu.svlete
<script>
  export let shochu;
</script>

<tr>
  <td>{shochu.name}</td>
  <td>{shochu.brewery}</td>
</tr>

ここで偶数行には背景色を指定したいと思います。Shochu.svelte

/src/lib/components/Shochu.svlete
<style>
tr:nth-of-type(2n) {
  background-color: #ffcccc;
}
</style>

とすれば済む話ですが、本記事では :global() 修飾子の話がしたいので、ShochuList.svelte にスタイルを指定したいと思います。

ShochuList.svelte コンポーネント内の偶数番目の tr に背景色を指定したいと思い、以下のように記述しました。

/src/lib/components/ShochuList.svlete
<style>
tbody:global(tr:nth-of-type(2n)) {
  background-color: #ffcccc;
}
</style>

ところが以下のエラーが発生しました。

:global(...) not at the start of a selector sequence should not contain type or universal selectors svelte(css-invalid-global-selector-position)

セレクターの位置に要素型セレクター (この場合 tr) やユニバーサルセレクター ( * ) をいれるな、と言われています。

ちょっとよくわからなかったので色々試してみました。

:global() の前のセレクターと引数のセレクターで考える

公式ドキュメントでは :global() について多くは語られていませんが、もう一度眺めてみます。

div :global(strong) {
	color: goldenrod;
}

p:global(.red) {
}

Svelte components • Docs • Svelte

すると前者の div :global(strong) は普通に CSS で書く場合 div strong を意図しており、後者の p:global(.red)p.red を意図していることに気づき、:global() 修飾子の引数のセレクタによって、:global() 修飾子の前のセレクタとの間に結合子(結合子 - ウェブ開発を学ぶ | MDN)が必要なのではと気づきました。

従って上記公式ドキュメントの div :global(strong)div:global(strong) の間のスペースは単なるスペースではなく、子孫結合子(結合子 - ウェブ開発を学ぶ | MDN)ということです。

試しに先ほどの ShochuList.svelte のスタイルで tbody:global() 修飾子の間に子結合子(結合子 - ウェブ開発を学ぶ | MDN)を入れてみたところ、エラーが消え想定通り、偶数行に背景色が付きました。

/src/lib/components/ShochuList.svlete
<style>
tbody>:global(tr:nth-of-type(2n)) {
  background-color: #ffcccc;
}
</style>

これにより、:global() 修飾子は前のセレクターと引数のセレクターで、通常 CSS で書くときのことを意識すると正しくワークすることがわかりました。

様々なセレクターの組み合わせパターン

:global() 修飾子の使い方がわかりましたのでセレクタのパターンによる書き方を紹介します。(そんなの :global() 使って書かないだろみたいなのもありますが)

ID セレクター

/* <div id="top"> を選択 */
div:global(#top) {}

/* <div> 内にある id="top" の要素を選択 */
div :global(.box) {}

クラスセレクター

/* <div class="box"> を選択 */
div:global(.box) {}

/* <div> 内にある class="box" の要素を選択 */
div :global(.box) {}

属性セレクター

/* <input disabled> を選択 */
input:global([disabled]) {}

/* <div> 内にある href 属性を持つ要素を選択 */
div :global([href]) {}

疑似クラス

/* 兄弟要素の <p> のうち最初の <p> を選択 */ 
p:global(:first-of-type)

できない書き方

試してみてできなかった書き方も紹介します。

global() 修飾子の前の結合子を引数には入れられない

先ほど 子結合子を付けて tbody>:global(tr:nth-of-type) とすることでワークしたと書きましたが、試しにこの子結合子を global() 修飾子の引数に入れて tbody:global(>tr:nth-of-type) としてみましたが、冒頭と同じエラーが出てダメでした。

global() 修飾子の前のセレクターとの結合子は前のセレクターと global() 修飾子の間に入れましょう。

global() 修飾子の引数内でセレクターリストは使えない

global() 修飾子の引数内だけでセレクターリスト使えたら便利だなと思い、table :global(th, td) のように書いてみましたが、以下のエラーの通り、:global() 修飾子は一つのセレクターしか含められないとのことでした…。

:global(...) must contain a single selector svelte(css-invalid-global-selector)

あとがき

Svelte コンポーネント <style> 内での :global() 修飾子の使い方について調べてみました。わかれば当たり前のような気もするのですが、少しハマったので記事にしてみました。