`<wa-badge>` の `role` 属性をスロットから移動し、スロットをラップ要素で包む
<wa-badge> コンポーネントで <slot> 要素に誤って付与されていた role="status" を仕様準拠の構造に修正しました。HTML仕様では <slot> への role 属性の付与は許可されていないため、各スロットを <span> でラップする構造に変更しています。
背景
#2163 で報告されたとおり、<slot> 要素への role 属性の付与はHTML仕様上、許可されていません。MDN の <slot> 技術仕様サマリーによれば、<slot> に付与できる属性は限定されており、ARIAロールはその対象外です。
従来の実装では、バッジのベーススロットに role="status" を直接付与していました。これはスクリーンリーダーなどの支援技術に対してライブリージョンとしてのセマンティクスを伝えることを意図した設計でしたが、<slot> はShadow DOMのコンテンツ投影機構であり、通常のHTMLフロー要素と同様のARIA属性の意味論を持ちません。
Issueでは role="status" もしくは aria-live="polite" を <wa-badge> ホスト要素自体に付与する方法が代替案として提案されていましたが、今回の修正では <span> ラッパーを挟む方式が採用されています。
技術的な変更
badge.ts の render() メソッドにおいて、すべてのスロットが <span> 要素でラップされ、role と part はラッパー側に移動しました。
変更前:
render() {
return html`
<slot name="start" part="start"></slot>
<slot part="base" role="status"></slot>
<slot name="end" part="end"></slot>
`;
}
変更後:
render() {
return html`
<span part="start">
<slot name="start"></slot>
</span>
<span part="base" role="status">
<slot></slot>
</span>
<span part="end">
<slot name="end"></slot>
</span>
`;
}
各スロットが <span> でラップされ、part 属性はラッパー側に付与されています。また、role="status" はベーススロットから対応する <span> に移動しており、HTMLの仕様に適合した構造になっています。
part 属性の付与先が <slot> から <span> に変わったことで、CSSパーツセレクタ(::part(base) 等)でスタイルを当てているユーザーは対象要素の型が変わる点に注意が必要です。ただし、セレクタ名自体は変更されていないため、通常のスタイリングには影響しません。
設計判断
<span> ラッパーを挟む方式 が採用され、role をホスト要素に移動する案は選択されていませんでした。
Issueではホスト要素 <wa-badge> への role 付与も提案されていましたが、今回の修正はWebAwesomeのコンポーネント実装規約(「slots per the WA convention」)に沿ったラップ構造の統一を優先しています。<span> を挟むことで、role や part をShadow DOM内の具体的なレイアウト要素に紐付けつつ、<slot> 自体は純粋なコンテンツ投影の役割に限定する設計になっています。
このアプローチはstart・base・endの3スロット全てに対して一貫して適用されており、ラッパー要素の有無による構造的なばらつきをなくしています。
まとめ
本PRは仕様違反のマークアップを修正しながら、コンポーネント内部の構造をWebAwesomeの規約に沿って統一した変更です。<slot> を純粋なコンテンツ投影機構として扱い、ARIA属性やCSSパーツはラッパー要素側に集約するパターンは、Shadow DOMを用いたコンポーネント設計における明快な指針を示しています。