CSS Popover API 活用ガイド - JavaScriptなしで実装するモダンなポップオーバー


CSS Popover APIとは

CSS Popover APIは、ポップオーバー(一時的に表示されるコンテンツ)をJavaScriptなしで実装できる新しいWeb標準APIです。2023年にChrome 114で正式にサポートされ、モダンブラウザでの採用が進んでいます。

従来、ポップオーバーやツールチップ、モーダルダイアログなどを実装するには、複雑なJavaScriptコードやサードパーティライブラリが必要でした。Popover APIを使用することで、HTML属性だけでこれらの機能を簡単に実装できるようになります。

主な特徴

  • ゼロJavaScript: HTML属性のみで動作
  • 自動的なz-index管理: トップレイヤーに自動配置
  • キーボード操作対応: Escキーで閉じる動作が標準搭載
  • アクセシビリティ: ARIA属性の自動設定
  • 軽量: 追加のライブラリ不要

基本的な使い方

シンプルなポップオーバー

最もシンプルなポップオーバーの実装例です。

<button popovertarget="my-popover">ヒントを表示</button>

<div id="my-popover" popover>
  <p>これはポップオーバーコンテンツです。</p>
  <p>Escキーまたは外側をクリックで閉じます。</p>
</div>

ポイント:

  • popover属性をポップオーバー要素に追加
  • popovertarget属性でトリガーボタンとポップオーバーを関連付け
  • idで要素を識別

ポップオーバーの動作モード

Popover APIには3つの動作モードがあります。

<!-- 自動モード(デフォルト): 外側クリックやEscで閉じる -->
<div id="auto-popover" popover="auto">
  自動的に閉じるポップオーバー
</div>

<!-- 手動モード: 明示的な操作でのみ閉じる -->
<div id="manual-popover" popover="manual">
  手動で閉じる必要があるポップオーバー
</div>

<!-- 暗黙的な自動モード -->
<div id="implicit-popover" popover>
  popover="auto"と同じ動作
</div>

autoモードの特徴:

  • 外側クリックで自動的に閉じる
  • Escキーで閉じる
  • 他のautoポップオーバーが開くと閉じる
  • 軽量なインタラクション向け(ツールチップ、ドロップダウンなど)

manualモードの特徴:

  • 明示的な閉じる操作が必要
  • 複数同時に開ける
  • 重要な情報の表示向け(警告、確認ダイアログなど)

ポップオーバーの開閉制御

トリガーボタンの動作を細かく制御できます。

<!-- 開く専用ボタン -->
<button popovertarget="menu" popovertargetaction="show">
  メニューを開く
</button>

<!-- 閉じる専用ボタン -->
<button popovertarget="menu" popovertargetaction="hide">
  メニューを閉じる
</button>

<!-- トグルボタン(デフォルト) -->
<button popovertarget="menu" popovertargetaction="toggle">
  メニューを切り替え
</button>

<div id="menu" popover>
  <h3>メニュー</h3>
  <ul>
    <li>項目1</li>
    <li>項目2</li>
    <li>項目3</li>
  </ul>
  <!-- ポップオーバー内に閉じるボタンを配置 -->
  <button popovertarget="menu" popovertargetaction="hide">
    閉じる
  </button>
</div>

スタイリング

基本的なスタイリング

ポップオーバーは通常のHTML要素なので、自由にスタイリングできます。

/* ポップオーバーの基本スタイル */
[popover] {
  padding: 1.5rem;
  border: 2px solid #333;
  border-radius: 8px;
  background: white;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  max-width: 400px;
}

/* トリガーボタンのスタイル */
[popovertarget] {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
}

[popovertarget]:hover {
  background: #0056b3;
}

アニメーション

開閉時のアニメーションを追加できます。

/* 開くアニメーション */
[popover]:popover-open {
  animation: slideIn 0.3s ease-out;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 閉じるアニメーション(@starting-styleを使用) */
@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: translateY(-10px);
  }
}

/* トランジション */
[popover] {
  transition: opacity 0.3s, transform 0.3s, display 0.3s allow-discrete;
}

バックドロップのスタイリング

::backdrop疑似要素でオーバーレイをカスタマイズできます。

[popover]::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(3px);
}

実践的な実装例

ツールチップ

情報表示用のシンプルなツールチップです。

<style>
  .tooltip-trigger {
    position: relative;
    text-decoration: underline;
    text-decoration-style: dotted;
    cursor: help;
  }

  .tooltip {
    padding: 0.5rem 0.75rem;
    background: #333;
    color: white;
    border: none;
    border-radius: 4px;
    font-size: 0.875rem;
    max-width: 200px;
  }

  .tooltip::backdrop {
    display: none;
  }
</style>

<p>
  この機能は
  <span class="tooltip-trigger">
    <button popovertarget="tooltip-1" class="tooltip-trigger">
      ベータ版
    </button>
  </span>
  です。
</p>

<div id="tooltip-1" popover class="tooltip">
  この機能は現在開発中です。予告なく変更される可能性があります。
</div>

ドロップダウンメニュー

ナビゲーション用のドロップダウンメニューです。

<style>
  .nav-button {
    padding: 0.5rem 1rem;
    background: transparent;
    border: 1px solid #ddd;
    border-radius: 4px;
    cursor: pointer;
  }

  .dropdown-menu {
    padding: 0;
    border: 1px solid #ddd;
    border-radius: 4px;
    background: white;
    min-width: 200px;
    list-style: none;
  }

  .dropdown-menu li {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid #eee;
    cursor: pointer;
  }

  .dropdown-menu li:last-child {
    border-bottom: none;
  }

  .dropdown-menu li:hover {
    background: #f5f5f5;
  }
</style>

<button popovertarget="user-menu" class="nav-button">
  ユーザーメニュー ▼
</button>

<ul id="user-menu" popover class="dropdown-menu">
  <li>プロフィール</li>
  <li>設定</li>
  <li>ヘルプ</li>
  <li>ログアウト</li>
</ul>

モーダルダイアログ風

重要な確認を求める場合に使用します。

<style>
  .dialog {
    padding: 2rem;
    border: none;
    border-radius: 8px;
    background: white;
    max-width: 500px;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
  }

  .dialog::backdrop {
    background: rgba(0, 0, 0, 0.6);
  }

  .dialog-header {
    margin: 0 0 1rem 0;
    font-size: 1.5rem;
    font-weight: bold;
  }

  .dialog-actions {
    margin-top: 1.5rem;
    display: flex;
    gap: 1rem;
    justify-content: flex-end;
  }

  .btn {
    padding: 0.5rem 1.5rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
  }

  .btn-primary {
    background: #dc3545;
    color: white;
  }

  .btn-secondary {
    background: #6c757d;
    color: white;
  }
</style>

<button popovertarget="confirm-dialog" class="btn btn-primary">
  アカウントを削除
</button>

<div id="confirm-dialog" popover="manual" class="dialog">
  <h2 class="dialog-header">本当に削除しますか?</h2>
  <p>この操作は取り消せません。アカウントとすべてのデータが完全に削除されます。</p>
  <div class="dialog-actions">
    <button popovertarget="confirm-dialog" popovertargetaction="hide" class="btn btn-secondary">
      キャンセル
    </button>
    <button class="btn btn-primary">削除する</button>
  </div>
</div>

JavaScriptとの連携

必要に応じてJavaScriptで制御することも可能です。

プログラムでの開閉

const popover = document.getElementById('my-popover');

// 表示
popover.showPopover();

// 非表示
popover.hidePopover();

// トグル
popover.togglePopover();

イベントリスナー

ポップオーバーの状態変化を検知できます。

const popover = document.getElementById('my-popover');

// 表示前
popover.addEventListener('beforetoggle', (event) => {
  console.log('状態:', event.newState); // "open" または "closed"
  console.log('前の状態:', event.oldState);

  // 開く前の処理
  if (event.newState === 'open') {
    console.log('ポップオーバーが開きます');
    // データの読み込みなど
  }
});

// 表示後
popover.addEventListener('toggle', (event) => {
  if (event.newState === 'open') {
    console.log('ポップオーバーが開きました');
  } else {
    console.log('ポップオーバーが閉じました');
  }
});

動的なコンテンツ読み込み

const popover = document.getElementById('dynamic-popover');
const trigger = document.querySelector('[popovertarget="dynamic-popover"]');

trigger.addEventListener('click', async () => {
  // データを取得
  const data = await fetchData();

  // ポップオーバーのコンテンツを更新
  popover.innerHTML = `
    <h3>${data.title}</h3>
    <p>${data.description}</p>
  `;
});

async function fetchData() {
  // APIからデータを取得(例)
  return {
    title: '最新情報',
    description: 'ポップオーバーの内容を動的に更新しました。'
  };
}

アクセシビリティ

Popover APIは基本的なアクセシビリティを自動で提供しますが、さらに改善できます。

ARIA属性の追加

<button
  popovertarget="accessible-popover"
  aria-label="詳細情報を表示"
  aria-describedby="popover-description">
  詳細
</button>

<div
  id="accessible-popover"
  popover
  role="dialog"
  aria-labelledby="popover-title"
  aria-describedby="popover-description">
  <h3 id="popover-title">詳細情報</h3>
  <p id="popover-description">
    この機能についての追加説明がここに表示されます。
  </p>
</div>

キーボードナビゲーション

<style>
  .menu-item {
    display: block;
    width: 100%;
    padding: 0.75rem 1rem;
    border: none;
    background: transparent;
    text-align: left;
    cursor: pointer;
  }

  .menu-item:focus {
    background: #e9ecef;
    outline: 2px solid #007bff;
    outline-offset: -2px;
  }
</style>

<button popovertarget="keyboard-menu" aria-haspopup="menu">
  メニュー
</button>

<div id="keyboard-menu" popover role="menu">
  <button class="menu-item" role="menuitem" tabindex="0">
    新規作成
  </button>
  <button class="menu-item" role="menuitem" tabindex="0">
    開く
  </button>
  <button class="menu-item" role="menuitem" tabindex="0">
    保存
  </button>
</div>

ブラウザサポートと代替手段

対応ブラウザ

  • Chrome/Edge: 114+
  • Safari: 17+
  • Firefox: 125+ (2024年4月リリース予定)

フィーチャー検出

if (HTMLElement.prototype.hasOwnProperty('popover')) {
  // Popover APIがサポートされている
  console.log('Popover API利用可能');
} else {
  // フォールバック処理
  console.log('Popover API非対応、代替実装を使用');
  usePolyfill();
}

ポリフィル

対応していないブラウザ向けのポリフィルも利用可能です。

<script type="module">
  import { applyPopoverPolyfill } from 'https://cdn.skypack.dev/@oddbird/popover-polyfill';

  // ポリフィルを適用
  applyPopoverPolyfill();
</script>

まとめ

CSS Popover APIは、モダンWebアプリケーションにおけるポップオーバー実装を大幅に簡素化します。

メリット

  • 開発効率の向上: JavaScriptライブラリが不要
  • パフォーマンス: ネイティブ実装のため高速
  • 保守性: シンプルなHTML属性で管理
  • アクセシビリティ: 標準的なキーボード操作をサポート

使用を検討すべきケース

  • ツールチップ、ヘルプテキスト
  • ドロップダウンメニュー
  • コンテキストメニュー
  • 軽量な確認ダイアログ
  • 通知やアラート

注意点

  • 複雑なモーダルダイアログは<dialog>要素の使用を検討
  • 位置指定が必要な場合はAnchor Positioning APIと組み合わせる
  • ブラウザサポートを確認し、必要に応じてポリフィルを使用

Popover APIを活用することで、よりシンプルで保守しやすいUIを実装できます。ぜひプロジェクトに導入してみてください。