chocolamint
@chocolamint

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

カスタム要素における template 要素内の style 要素で指定した ::slotted セレクタのスタイルが外部の全称セレクタ(*)に負ける

解決したいこと

カスタム要素を定義して、内部でスタイルを指定しようとしています。しかし思った通りに適用されず困っています。

template 要素内で用意した slot に差し込まれる li 要素へスタイルを当てたいです(コードは後述)。しかし、別の箇所で指定されている、いわゆる「リセットCSS」である

* { margin: 0; padding: 0; }

というスタイルに邪魔され、margin / padding が設定できなくて困っています。

一般に、全称セレクタは詳細度が 0-0-0 であり、要素セレクタは 0-0-1 であると理解しているのですが、何故か全称セレクタの指定が要素セレクタの指定を打ち消してしまいます。

  • なぜ全称セレクタの指定が優先されてしまうのか
  • どのように解決するのが一般的な手法なのか

を知りたいです。

発生している問題・エラー

全称セレクタで指定された指定が優先されています。

  • 期待している結果: li 要素の文字が青になり margin30px 取られる
  • 実際の結果: li 要素の文字は赤く、margin0 のままである

image.png

開発者ツールでも全称セレクタの指定がより優先度の高いものとして表示されます(が、詳細度だけ見ると勝っているはずなので理由がよくわかりません。::slotted() であることが関係している?)。

image.png

Windows 10 の Chrome, Firefox で再現しています。バージョンはあまり関係なさそうなので割愛します。

該当するソースコード

https://codepen.io/chocolamint/pen/poMbYJd で再現可能です。

<my-custom>
  <li slot="items">テスト</li>
  <li slot="items">テスト2</li>
</my-custom>
<template id="tm">
  <ul>
    <slot name="items"></slot>
  </ul>
  <style>
    ::slotted(li) {
      margin: 30px;
      color: blue;
      /* 全称セレクタで指定のないスタイルはきちんと適用されるのでセレクタ自体は間違っていない? */
      background: #F0F0FF;
    }
  </style>
</template>
* {
  margin: 0;
  padding: 0;
  color: red;
}
customElements.define(
  "my-custom",
  class MyCustomElement extends HTMLElement {
    connectedCallback() {
      const template = document.querySelector("#tm").content;
      const shadowRoot = this.attachShadow({ mode: "open" });
      shadowRoot.appendChild(template.cloneNode(true));
    }
  }
);

自分で試したこと

2

1Answer

仕様書によると、カスケード規則は Origin and Importance の次に Context があります。つまり、詳細度より強い規則です。
その記述は以下のようになっています。

When comparing two declarations that are sourced from different encapsulation contexts, then for normal rules the declaration from the outer context wins, and for important rules the declaration from the inner context wins. For this purpose, [DOM] tree contexts are considered to be nested in shadow-including tree order.
CSS Cascading and Inheritance Level 5

二つの宣言が、異なるカプセル化されたコンテキストのソースであった場合、通常の宣言は外側が勝ち、!importantは内側が勝つ、と記述されているように読めます。

こちらが原因ではありませんか?

2Like

Comments

  1. @chocolamint

    Questioner

    ありがとうございます!!
    めちゃくちゃそれっぽいですね…!確認してみます!😭🙏すごく助かりました

  2. @chocolamint

    Questioner

    なんだかこの記述からすると、ウェブコンポーネントで内部に隠蔽したい DOM 要素のスタイルが存在する場合は !important ルールで強制することが想定されてそうに読めますね…🤔

  3. @chocolamint

    Questioner

    内部の DOM を隠蔽したいのに slot で指定していることが本来の考え方からズレてる気がしてきました。
    普通に shadow DOM にアタッチした要素のスタイルは外部からの影響を(継承は別として)受けないようですし、slot は外から要素を差し込む仕組みなので外部の CSS が優先的に適用される、と考えられそうです。

    だいぶ理解できてきたので、質問はクローズさせていただきます。
    非常に助かりました、ありがとうございます!

  4. お役に立てたようで何よりです。

    ウェブコンポーネントはほとんどテストぐらいしか使ったことがないので運用がわからないですが、仕様書を読んで同じような疑問は抱きました。
    おっしゃる通り、コンポーネントの中でデフォルトのスタイリングをして、スロットに関しては外のドキュメントから上書きをするという思想なのでしょうけれど、コンポーネントの中に閉じ込めたい気もするのでリセットCSSに上書きされるのはやや違和感ありますね…… ::part()との関係がややこしそう……?

  5. @chocolamint

    Questioner

    それですね…
    急遽勉強中だったので ::part は実は読み飛ばしてたのですが、その辺もきちんと勉強して全体像を把握した方が良さそうですね😓(書いてることがやたら難しくて…)
    React 全盛期なので標準のウェブコンポーネントのノウハウを書いている方も少ないのもなかなか厳しいところです

Your answer might help someone💌