ビューポート対応のプルダウン

表示されたアクション パネルまたはポップオーバー ボタン グループは、ユーザーが最小限のスペースで追加機能にアクセスできる便利なパターンです。このオーバーレイ パターンは、ビューポートの制約に適応しながら、パネルをトリガー要素に固定したままにする必要があるため、レイアウトが複雑になります。従来は、位置を計算して衝突を処理するために、複雑な JavaScript ライブラリ(Popper.js や Floating UI など)が必要でした。

CSS アンカー ポジショニングは、これらの関係を完全に CSS で処理する、宣言型でパフォーマンスが最適化された方法を提供します。これにより、ブラウザはポジショニングとオーバーフロー ロジックをネイティブに管理できます。

1. ボタンとパネルの関係を定義する

最初のステップは、Popover API を使用してオーバーレイ コンテナを開くトリガーボタンを作成することです。

必須のユーザー補助機能の区別: このパターンは、真の ARIA メニューではなく、ポップオーバー内に表示されるボタン グループを明示的にモデル化します。対応するキーボード ナビゲーション コントラクト(アイテム間の空間矢印キー ナビゲーションの処理など)を完全に実装していない限り、role="menu" または role="menuitem" を適用しないでください。同じ理由で、aria-haspopup をトリガーに追加しないでください。その値(menu、listbox、tree、grid、または以前の true)は、ターゲットが一致するロールを公開することを保証するものであり、このパターンは公開しません。

<button popovertarget="action-panel">
  Open Actions
</button>
<!-- Use the Popover API (`popover="auto"`) for the overlay to ensure it is placed in the top layer and handled accessibly by the browser. -->
<div id="action-panel" popover="auto" class="panel">
  <button class="action-item" type="button">Edit</button>
</div>

これにより、ボタンとパネルの間に暗黙的なアンカー関連付けが作成されるため、ボタンに anchor-name を、パネルに position-anchor を指定しなくても、パネルをボタンを基準としてネイティブに配置できます。

2. position-area を使用したポジショニング

top / left オフセットを手動で指定する代わりに、position-area を使用して、アンカーを基準とする 3x3 グリッドにターゲットを配置します。

.panel {
  /* 
     Position the panel below the anchor (block-end), 
     aligned to the start of the anchor and spanning to its end (span-inline-end).
  */
  position-area: block-end span-inline-end;
  
  /* Reset insets to allow the grid to take control */
  inset: auto;
}

RTL とさまざまな記述モードを自動的にサポートするには、物理キーワード(left、top)よりも論理キーワード(span-inline-end、block-start)を使用することをおすすめします。

必須: position-area で物理キーワードと論理キーワードを混在させないでください。

3. エッジ レジリエンス(フォールバック)を実装する

パネルが画面の端で切れないようにするには、デフォルトの位置がオーバーフローした場合にブラウザが試行する「トライ タクティクス」を定義します。

.panel {
  /* 
     If the panel overflows the bottom, flip it to the top (flip-block).
     If it overflows the inline edges, flip it horizontally (flip-inline).
     If it overflows both (e.g., in a corner), flip on both axes (flip-block flip-inline).
  */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

ドロップダウンまたはフライアウトに、選択されたフォールバック位置に応じてスタイルを変更する必要がある方向矢印または枠線が含まれている場合は、位置認識ツールチップをご覧ください。JavaScript を使用せずにダイアログとポップオーバーを宣言的に呼び出す方法については、宣言型のダイアログとポップオーバーの制御をご覧ください。

代替戦略

Popover のフォールバックとブラウザ サポート

Popover API は主にプログレッシブ エンハンスメントですが、その定義動作である最上位レイヤのプロモーション、ライトディスミス、popovertarget 呼び出しには、CSS のみの同等の機能がありません。古いブラウザでは、ポリフィルが必要です。ポリフィルを配布したくない場合は、手動でフォールバックする必要があります。

ポリフィル: 古いブラウザで popover 属性をサポートするには、条件付きで @oddbird/popover-polyfill を読み込みます。必須: HTMLElement.prototype の popover プロパティをチェックして機能を検出し、ネイティブ サポートがない場合にのみポリフィルを読み込みます。無条件に読み込まないでください。

バンドラまたはインポート マップを使用する場合:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

バンドラを使用しない場合は、<script type="module"> 内の CDN からインポートします。

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

スタイリングに関する注意事項: ポリフィルは実際の :popover-open 疑似クラスを定義できないため、代わりに .\:popover-open クラスを適用します。必須: :is() または :where() を使用して 2 つを結合します。そうしないと、:popover-open がないブラウザではルール全体が破棄されます。

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

または、ポリフィルを使用しないレガシー フォールバックの場合は、position: fixed を使用して getBoundingClientRect() で座標を手動で計算するか、ユースケースで許容される場合は inset: auto でデフォルトの配置を使用します。

アンカー位置指定に対応していないブラウザをサポートするには、適切な位置を設定する必要があります。デフォルトでは、ポップオーバーは画面の中央に配置されます。これは、ユースケースによっては機能する可能性があります。

ユースケースによっては、@oddbird/css-anchor-positioning ポリフィルを使用できる場合があります。これにより、アンカー配置のユースケースの一部がサポートされます。暗黙的なアンカーはサポートされていないため、トリガーにアンカー名を追加する必要があります。また、position-area はポリフィルによるポップオーバーではサポートされていないため、目的のインセットで anchor() を使用する必要があります。

<!-- MANDATORY: Conditionally install the anchor positioning polyfill -->
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    await import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>
.panel {
  /* Mandatory: use explicit anchor name */
  position-anchor: --kebab-anchor;
  /* Mandatory: use insets rather than position-area for positioning */
  bottom: auto;
  right: auto;
  top: anchor(bottom);
  left: anchor(left);
  margin: 0;
}