カラーパレットのスウォッチスタイルをShadow DOM外のネイティブ要素で修正
カラーパレットページのスウォッチ表示が崩れていた問題を、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-button は position: 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-button を position: absolute; inset: 0 で配置する手法により、wa-copy-button の外側のコンテナ(.palette-swatch)がレイアウトの基準となり、スウォッチ全体をクリック可能な領域として確実に機能させられます。
アクセシビリティの観点では、copy-label 属性と <span class="sr-only"> の組み合わせから <button aria-label="..."> への移行により、スクリーンリーダーへのラベル提供をHTML標準の方法に統一しています。
まとめ
この修正は、Web Componentsの::part()によるスタイリングの限界に対して、スロットへのネイティブ要素挿入という実用的な解決策を採用した変更です。コンポーネントのカプセル化を維持しながら、外部から完全なスタイル制御を実現するアプローチとして、Web Componentsを活用したUIライブラリ開発における典型的なパターンの一例といえます。