リッチメディア ピッカー(カスタマイズ可能な選択)

ネイティブの <select> 要素は、これまでスタイル設定が難しく、書式なしテキストのオプションしか含めることができませんでした。appearance: base-select プロパティは、<select> 要素のカスタマイズ可能な状態を有効にする宣言型の CSS のみの方法を提供します。これにより、デベロッパーは、ネイティブのキーボード アクセシビリティとフォーム統合を維持しながら、画像、SVG、複雑なレイアウトなどのリッチな HTML コンテンツを <option> 要素内に含めることができます。このパターンを使用すると、重いカスタムビルドの選択コンポーネントを標準のネイティブ要素に置き換えることができます。

実装方法

Customizable Select API を使用してリッチメディアの選択を実装するには:

  1. 基本スタイルを有効にする: ::picker(select) 疑似要素を使用して、<select> 要素とその内部ピッカーの両方に appearance: base-select を適用します。これにより、<select> 内のコンテンツに対するブラウザの HTML パーサーが変更されます。
  2. ボタンのコンテンツを定義する: <select> 内で標準の <button> 要素と <selectedcontent> 要素を使用して、選択ツールが閉じているときに表示される内容を定義します。<selectedcontent> 要素は、選択したオプションのコンテンツを自動的にミラーリングします。これは、現在選択されているリッチ コンテンツをボタンに表示する場合に必要です。
  3. オプション内でリッチ コンテンツを使用する: <option> タグ内に画像、SVG、その他の HTML タグを配置できるようになりました。この API が導入される前は、ブラウザは <option> タグからタグを削除し、書式なしテキストのみをレンダリングしていました。
  4. ポップオーバーのスタイルを設定する: ::picker(select) 疑似要素をターゲットにして、プルダウン オプション リストのスタイルを設定します。最上位レイヤでレンダリングされるため、z-index を操作する必要はありません。オプションは、Anchor Positioning API を使用してネイティブに配置されます。

サンプルコード: リッチロール ピッカー

<label for="role-picker">Select your role</label>
<select class="custom-select" name="role" id="role-picker">
  <button>
    <selectedcontent></selectedcontent> <!-- Mirrors the selected option's content automatically so you do not need JS to update the button -->
  </button>

  <!-- Define concise aria-label values on options whose mirrored rich content would otherwise read awkwardly as a concatenated string -->
  <option value="frontend" aria-label="Frontend Developer">
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
    </svg>
    <div class="option-text">
      <span class="option-title">Frontend Developer</span>
      <span class="option-desc">React, Vue, CSS</span>
    </div>
  </option>

  <option value="backend" aria-label="Backend Developer">
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <rect x="2" y="12" width="20" height="14" rx="2" ry="2"></rect>
    </svg>
    <div class="option-text">
      <span class="option-title">Backend Developer</span>
      <span class="option-desc">Node.js, Python</span>
    </div>
  </option>
</select>
select.custom-select,
select.custom-select::picker(select) {
  appearance: base-select; /* MUST opt-in both the select and its picker to enable the customizable state, otherwise browser standard rendering applies */
}

select.custom-select {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 12px;
  background-color: #1e293b;
  border: 1px solid #334155;
  border-radius: 8px;
  color: #f1f5f9;
  cursor: pointer;
}

select.custom-select::picker(select) {
  background-color: #0f172a;
  border: 1px solid #334155;
  border-radius: 8px;
  padding: 8px;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
  width: anchor-size(width); /* Uses Anchor Positioning API to keep the dropdown precisely aligned to the button trigger width */
}

select.custom-select option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}

select.custom-select option:hover {
  background-color: #1e293b;
}

/* Remove standard OS checkmark for base-select */
select.custom-select option::before {
  display: none;
}

/* MANDATORY: Provide multiple visual indicators (e.g., prominent background color and bold title font) to communicate the checked state cleanly */
select.custom-select option:checked {
  background-color: #3b82f6;
  color: #ffffff;
}
select.custom-select option:checked .option-title {
  font-weight: 700;
}

戦略的な実装とベスト プラクティス

  • 推奨: 以前は重い JavaScript UI フレームワークでのみ可能だった複雑なオプション レイアウト(アイコン、説明、画像)が必要な場合は、appearance: base-select を使用します。
  • パフォーマンスの遅延に気づいた場合は、アドホック要素を使用しないでください。ブラウザはネイティブ キーボード フォーカスをネイティブに処理します。
  • 最上位レイヤのレンダリングを考慮する。選択ツールは最上位レイヤにレンダリングされます。つまり、ページ コンテンツの相対的な z-index をオーバーライドします。
  • .custom-select selectedcontent .option-desc { display: none; } のスタイルを設定して、ボタンの状態の二次的な詳細(説明など)がスペースを取りすぎる場合は、非表示にする。
  • レイアウトの動作をテストします。appearance: base-select を設定すると、最も長いオプションの幅に基づいて選択要素のサイズを決定するデフォルトのブラウザ動作が削除されます。レイアウト シフトを防ぐために、固定幅を設定するか、flex / grid 制約を使用する必要がある場合があります。
  • <select> に name 属性と関連付けられた <label> があることを確認します。これにより、カスタム UI を使用している場合でも、コンポーネントはスクリーン リーダーにアクセス可能であり、標準のフォーム送信で正しく動作します。
  • オプションで標準の value 属性を使用して、フォームの送信が以前とまったく同じように機能するようにします。

代替戦略

カスタマイズ可能な <select> のフォールバックとブラウザ サポート

appearance: base-select をまだサポートしていないブラウザでは、<select> 要素は標準のオペレーティング システムのプルダウンに適切にフォールバックされます。

  • テキスト以外のコンテンツが無視される: 古いブラウザでは、<option> タグ内の HTML タグ(<svg> や <div> など)が削除され、テキストノードのみがレンダリングされます。<option> のテキスト コンテンツが、それ自体で読みやすく、意味のあるものになるようにします。
  • HTML 構造の処理: 標準のパーサーは、<select> 内の <button> タグと <selectedcontent> タグを無視するか、無効として扱うことがあります。標準テキストを読み取り可能なフォールバックとして表示する場合、プログレッシブ エンハンスメントに重い JavaScript ポリフィルは厳密には必要ありません。
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});