カラーパレットのスウォッチスタイルをShadow DOM外のネイティブ要素で修正

shoelace-style/webawesome

カラーパレットページのスウォッチ表示が崩れていた問題を、wa-copy-buttonのShadow DOMの::part(button)へのスタイル適用から、スロットに挿入したネイティブ<button>要素へのスタイル適用へ切り替えることで修正しました。

背景

カラーパレットページでは、各カラースウォッチを wa-copy-button コンポーネントで包み、クリックでCSS変数値をクリップボードにコピーできるUIを提供していました。このスウォッチの見た目は ::part(button) セレクタを使ってコンポーネント内部のShadow DOM要素にスタイルを当てる方式で実現されていましたが、スウォッチのスタイルが正しく表示されない問題が発生していました。

PRに添付されたBefore/Afterのスクリーンショットからも、修正前はスウォッチのサイズや外観が意図通りにレンダリングされていないことが確認できます。

技術的な変更

::part(button) によるShadow DOM内部へのスタイル適用をやめ、スロットに挿入するネイティブ <button> 要素に直接スタイルを当てる方式に切り替えました。

HTMLテンプレート側では、copy-label 属性と <span class="sr-only"> を削除し、代わりにスロットコンテンツとして <button class="swatch-button"> を挿入するように変更しています。アクセシビリティのためのラベルは aria-label 属性に移動しました。

変更前:

<wa-copy-button
  class="palette-swatch"
  copy-label="{{ color }} {{ tint }}"
  value="var(--wa-color-{{ color }}-{{ tint }})"
  style="--color: var(--wa-color-{{ color }}-{{ tint }}); --tint: '{{ tint }}'"
>
  <span class="sr-only">--wa-color-{{ color }}-{{ tint }}</span>
</wa-copy-button>

変更後:

<wa-copy-button
  class="palette-swatch"
  value="var(--wa-color-{{ color }}-{{ tint }})"
  style="--color: var(--wa-color-{{ color }}-{{ tint }}); --tint: '{{ tint }}'"
>
  <button class="swatch-button" aria-label="{{ color }} {{ tint }} (click to copy)"></button>
</wa-copy-button>

CSS側では、.palette-swatch から padding: 0::part(button) ブロックを削除し、代わりに .swatch-button クラスを独立したルールとして定義しています。.palette-swatch 自体は display: block に変更され、.swatch-buttonposition: absolute; inset: 0 で親要素全体を覆う形でレイアウトされます。

変更前:

.palette-swatch {
  padding: 0;
  aspect-ratio: 1.75 / 1;
  position: relative;

  &::before { /* tint番号を表示 */ }

  &::part(button) {
    width: 100%;
    height: 100%;
    cursor: pointer;
    background-color: var(--color);
    border-radius: var(--wa-border-radius-m);
    transition: transform 0.1s ease, box-shadow 0.1s ease;
  }
}

変更後:

.palette-swatch {
  display: block;
  position: relative;
  aspect-ratio: 1.75 / 1;

  &::before { /* tint番号を表示 */ }
}

.swatch-button {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  cursor: pointer;
  background-color: var(--color);
  border-radius: var(--wa-border-radius-m);
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}

設計判断

Shadow DOMの::part()経由のスタイリングからスロットへのネイティブ要素挿入へのアプローチの切り替えが、今回の核心的な設計判断です。

::part() はShadow DOM内の特定要素にCSSを適用できる標準的な手段ですが、コンポーネント側が part 属性を公開していない場合や、適用できるCSSプロパティの挙動がブラウザや実装によって制限される場合があります。スロットに挿入したネイティブ要素であれば、通常のCSSスコープ内でスタイルを完全にコントロールできます。また、.swatch-buttonposition: absolute; inset: 0 で配置する手法により、wa-copy-button の外側のコンテナ(.palette-swatch)がレイアウトの基準となり、スウォッチ全体をクリック可能な領域として確実に機能させられます。

アクセシビリティの観点では、copy-label 属性と <span class="sr-only"> の組み合わせから <button aria-label="..."> への移行により、スクリーンリーダーへのラベル提供をHTML標準の方法に統一しています。

まとめ

この修正は、Web Componentsの::part()によるスタイリングの限界に対して、スロットへのネイティブ要素挿入という実用的な解決策を採用した変更です。コンポーネントのカプセル化を維持しながら、外部から完全なスタイル制御を実現するアプローチとして、Web Componentsを活用したUIライブラリ開発における典型的なパターンの一例といえます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
b0f3a8bd

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文(総論)→各論(背景・技術詳細・設計判断)→まとめ(結論)」という理想的な3部構成が明確に守られています。各セクションの役割が明確で、非常に読みやすい構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```html:filepath`, `css:filepath`)およびGitHubのPRリンク記法(`[PR #2273](URL)`)がガイドラインに準拠して正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Shadow DOM、`::part()`、スロット、`aria-label`といった技術用語を前提としており、専門知識を持つエンジニアという対象読者に完全に適合しています。冗長な説明がなく、簡潔です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論から各論へと展開されており、各段落はトピックセンテンスで始まっています。1段落1トピックの原則が守られ、段落長も適切であるため、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているHTMLとCSSのコードブロックは、提供されたDiffの内容(変更前・変更後)を正確に反映しています。ファイルパスの指定も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Shadow DOM」「::part()」「スロット」「aria-label」などのWeb Components関連の技術用語が、文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「`::part()`によるスタイリングからスロット内のネイティブ要素へのスタイリングへの切り替え」という技術的な説明は、Diffの内容と完全に一致しており、論理的で正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(表示崩れの修正、実装方法の変更、アクセシビリティの改善)は、PRのタイトル、Diff、スクリーンショットから直接裏付けが取れます。ハルシネーション(捏造)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#2273)が正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRのタイトル「Fix swatch styles」の内容をより具体的に「どのように修正したか」まで含めて表現しており、PRの内容と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事はPRで提供された情報のみに基づいており、バージョン情報やリリース予定など、PR外の知識を持ち出してはいません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

完了した変更に対して「修正しました」といった過去形の表現が使われており、時間表現は正確です。