`wa-option`のdisabled状態をCSS Custom Stateで管理する修正

shoelace-style/webawesome

wa-optiondisabledプロパティをJavaScriptから設定しても見た目が変わらないバグを、CSS Custom State:state(disabled))の導入によって修正しました。属性の反映に依存したスタイル定義から、より適切なカスタム状態ベースの管理へと移行しています。

背景

disabled属性とdisabledプロパティの動作が一致しないという問題が#1997で報告されていました。

<wa-option>では、HTMLのdisabled属性はDOMに反映(reflect)されない設計になっていました。しかしCSSのスタイル定義では:host([disabled])という属性セレクタを使用していたため、JavaScriptでoption.disabled = trueとプロパティを設定しても属性がDOMに追加されず、スタイルが適用されませんでした。その結果、ARIAのaria-disabledは正しく設定されてクリックも無効化されるにもかかわらず、グレーアウトやcursor: not-allowedが表示されないという不整合な状態が生じていました。

根本的な原因は、disabled属性のreflectなしという設計に対して、スタイルが属性の存在に依存していた点にあります。

技術的な変更

スタイル定義をDOM属性セレクタからCSS Custom Stateセレクタへ置き換え、コンポーネント側でカスタム状態を同期するコードを追加しています。

option.styles.tsでは、すべての:host([disabled])セレクタが:host(:state(disabled))に変更されました。

変更前:

:host(:not([disabled], :state(current)):is(:state(hover), :hover)) {
  background-color: var(--wa-color-neutral-fill-normal);
  color: var(--wa-color-neutral-on-normal);
}

:host([disabled]:state(current)) {
  background-color: var(--wa-color-brand-fill-loud);
  color: var(--wa-color-brand-on-loud);
  opacity: 1;
}

:host([disabled]) {
  outline: none;
  opacity: 0.5;
  cursor: not-allowed;
}

変更後:

:host(:not(:state(disabled), :state(current)):is(:state(hover), :hover)) {
  background-color: var(--wa-color-neutral-fill-normal);
  color: var(--wa-color-neutral-on-normal);
}

:host(:state(disabled):state(current)) {
  background-color: var(--wa-color-brand-fill-loud);
  color: var(--wa-color-brand-on-loud);
  opacity: 1;
}

:host(:state(disabled)) {
  outline: none;
  opacity: 0.5;
  cursor: not-allowed;
}

option.tsでは、disabledプロパティの変更を検知するchangedPropertiesハンドラに、customStates.set による状態の同期処理を1行追加しています。

追加コード:

if (changedProperties.has('disabled')) {
  this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
  this.customStates.set('disabled', this.disabled);  // 追加
}

JSDocにも@cssstate disabledのアノテーションが追加され、コンポーネントのパブリックAPIとして明示されるようになりました。

設計判断

属性reflectの追加ではなく、CSS Custom Stateの導入という修正方針が選択されました。

PRの説明では「disabled属性はreflectされないが、スタイルがそれに依存していた。より適切な修正はdisabled用のカスタム状態を使うことだ」と明言されています。disabled属性のreflectを有効にする方法もありますが、この選択肢は採用されていません。Custom Stateはコンポーネント内部の状態をCSSに公開するために設計された仕組みであり、DOM属性よりも意味的に正確な手段です。すでにcurrentselectedhoverがCustom Stateとして実装されていたことからも、disabledを同じ管理方式に揃えることはコンポーネント設計の一貫性を保つうえで自然な判断といえます。

まとめ

DOM属性へのreflectに依存せずCustom Stateで状態を管理するアプローチにより、disabledプロパティと属性の見た目が統一されるとともに、スタイルとコンポーネント状態の結合をより明確に分離できました。<wa-button>でも同様にdisabledカスタム状態が導入されていることからも、このパターンがWebAwesomeコンポーネントライブラリ全体の標準的な設計として定着しつつあることがわかります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
00f1dedf

この記事は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リンク記法の正確性

ファイル名付きのシンタックスハイライト(```typescript:path/to/file.ts)およびGitHubのIssue/PRへのリンク記法は、ガイドラインに完全に準拠しています。

対象読者への適合性 ✓ PASS

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

CSS Custom State、属性のreflect、コンポーネントの状態管理といったトピックは、専門知識を持つエンジニアという対象読者に完全に適合しています。不必要な初心者向けの説明はありません。

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

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

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まっています。1段落1トピックの原則が守られ、段落長も適切です。非常に可読性の高い文章です。

Diff内容との照合 ✓ PASS

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

記事内で引用されているすべてのコードブロック(変更前・変更後・追加コード)は、提供されたDiffの内容と完全に一致しています。ファイルパスの記載も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「CSS Custom State」「reflect」「属性セレクタ」「customStates」などの技術用語が、文脈に沿って正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

「属性がreflectされないためにスタイルが適用されない」という問題の原因と、「Custom Stateを用いてコンポーネントの内部状態とスタイルを同期させる」という解決策の説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiff内のコード、さらにはchangelogの変更点によって裏付けられています。特に「<wa-button>でも同様の導入」という言及はchangelogのdiffから得られた情報であり、ハルシネーションではありません。素晴らしいです。

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

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

PR番号(#2230)や関連Issue番号(#1997)などの数値・固有名詞はすべて正確に記載されています。

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

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

記事のタイトル「`wa-option`のdisabled状態をCSS Custom Stateで管理する修正」は、PRの主題「Add disabled custom state to option」を的確に要約しており、内容との一貫性も保たれています。

外部知識の正確性 ✓ PASS

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

記事に含まれる情報はすべてPR内で提供された情報(Description, Diff)に基づいており、PRに記載のないバージョン情報やサポート状況などの外部知識を持ち込んでいません。

時間表現の正確性 ✓ PASS

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

「〜でした」「〜されていました」「〜として定着しつつある」など、過去の状態と現在の変更、そしてその影響を示す時間表現が正確に使われています。