Back

モダンCSS & JSを使ったアクセシブルなポップオーバーの作成

モダンCSS & JSを使ったアクセシブルなポップオーバーの作成

ポップオーバーは、ユーザーのワークフローを妨げることなくコンテキスト情報を提示しますが、多くの開発者にとってアクセシブルな実装は依然として課題です。レガシーコードのモダン化やコンポーネントライブラリの構築において、ポップオーバー、ツールチップ、モーダルの違いを理解することは、適切なユーザーエクスペリエンスを作成するために重要です。

この記事では、動的な位置決めからキーボードナビゲーションまで、モダンなCSSとJavaScriptを使用してアクセシブルなポップオーバーを構築する方法を説明し、複雑さを軽減するネイティブブラウザAPIについても探求します。

重要なポイント

  • ポップオーバーは、ホバー時に簡潔なヒントを表示するツールチップとは異なり、閉じられるまで持続するリッチでインタラクティブなコンテンツを表示します
  • ネイティブPopover APIは、組み込みのアクセシビリティ機能を提供しながらJavaScriptの複雑さを排除します
  • 適切なフォーカス管理とARIA属性は、キーボードナビゲーションに不可欠です
  • 動的な位置決めにより、ポップオーバーがビューポートの境界内に確実に表示されます

ポップオーバー vs. ツールチップ vs. モーダルの理解

ツールチップは、ホバー時に簡潔なヒントを提供し、通常は1行のテキストを含みます。ユーザーがカーソルを移動すると消え、インタラクティブな要素を含むことはできません。

ポップオーバーは、ヘッダー、段落、ボタン、フォームなど、よりリッチなコンテンツを表示します。明示的に閉じられるまで表示され続け、ユーザーはポップオーバーのコンテンツとその下のページの両方とやり取りできます。

モーダルは、背景を無効にすることで集中した体験を作成します。ユーザーはメインコンテンツに戻る前に、モーダルでのやり取りを完了する必要があります。

コア実装要件

ビューポート内での動的な位置決め

モダンなポップオーバーは、利用可能な画面スペースに適応する必要があります。ポップオーバーがビューポートの端を超えて拡張される場合、自動的に再配置される必要があります:

const positionPopover = (trigger, popover) => {
  const triggerRect = trigger.getBoundingClientRect()
  const popoverRect = popover.getBoundingClientRect()
  
  let top = triggerRect.bottom + 8
  let left = triggerRect.left
  
  // Flip to top if insufficient space below
  if (top + popoverRect.height > window.innerHeight) {
    top = triggerRect.top - popoverRect.height - 8
    popover.classList.add('popover--top')
  }
  
  // Adjust horizontal position
  if (left + popoverRect.width > window.innerWidth) {
    left = window.innerWidth - popoverRect.width - 16
  }
  
  popover.style.top = `${top}px`
  popover.style.left = `${left}px`
}

矢印の配置

CSSは、トリガー要素を指す視覚的な矢印を処理します:

.popover::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  background: inherit;
  border: inherit;
  transform: rotate(45deg);
  top: -7px;
  left: 20px;
  border-bottom: 0;
  border-right: 0;
}

.popover--top::after {
  top: auto;
  bottom: -7px;
  transform: rotate(225deg);
}

閉じる仕組み

アクセシブルなポップオーバーには複数の閉じる方法が必要です:

// Click outside
document.addEventListener('click', (e) => {
  if (!popover.contains(e.target) && !trigger.contains(e.target)) {
    closePopover()
  }
})

// ESC key
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && isPopoverOpen) {
    closePopover()
    trigger.focus() // Return focus to trigger
  }
})

ネイティブPopover API

Popover APIは、多くのJavaScriptの複雑さを排除します:

<button popovertarget="my-popover">Open Info</button>

<div id="my-popover" popover>
  <h3>Additional Information</h3>
  <p>This popover requires no JavaScript for basic functionality.</p>
  <button popovertarget="my-popover">Close</button>
</div>

このネイティブアプローチは、位置決め、閉じる処理、フォーカス管理を自動的に処理します。アクセシビリティを向上させるために、<dialog>要素と組み合わせます:

<dialog id="enhanced-popover" popover>
  <h2>Accessible Popover</h2>
  <p>Combining dialog with popover provides semantic meaning.</p>
  <button popovertarget="enhanced-popover">Close</button>
</dialog>

ライブラリ vs. ネイティブソリューションの比較

Popper.jsなどの従来のライブラリは広範囲な位置決めアルゴリズムを提供しますが、バンドルに15-30KBを追加します。ネイティブPopover APIは以下を提供します:

  • 基本機能に対するゼロJavaScript
  • 組み込みアクセシビリティ機能
  • 自動フォーカス管理
  • ブラウザ最適化された位置決め

複雑な位置決め要件については、ライブラリは依然として価値があります。標準的な使用例では、ネイティブソリューションが複雑さを大幅に軽減します。

重要なアクセシビリティの考慮事項

ARIA属性

ネイティブAPIを使わずにカスタムポップオーバーを構築する場合:

<button 
  aria-expanded="false"
  aria-controls="custom-popover"
  aria-haspopup="dialog">
  Open Popover
</button>

<div 
  id="custom-popover"
  role="dialog"
  aria-labelledby="popover-title"
  aria-modal="false">
  <h2 id="popover-title">Popover Title</h2>
  <!-- Content -->
</div>

フォーカス管理

適切なフォーカス順序により、キーボードユーザーが効果的にナビゲートできます:

const focusableElements = popover.querySelectorAll(
  'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
)

// Trap focus within popover
popover.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    const firstElement = focusableElements[0]
    const lastElement = focusableElements[focusableElements.length - 1]
    
    if (e.shiftKey && document.activeElement === firstElement) {
      e.preventDefault()
      lastElement.focus()
    } else if (!e.shiftKey && document.activeElement === lastElement) {
      e.preventDefault()
      firstElement.focus()
    }
  }
})

背景スクロールの防止

ネイティブAPIを使用する場合、CSSだけで背景スクロールを防ぐことができます:

body:has(dialog[popover]:popover-open) {
  overflow: hidden;
}

まとめ

アクセシブルなポップオーバーの構築には、ユーザーのニーズと技術的実装のバランスが必要です。ネイティブPopover APIは、アクセシビリティ標準を維持しながら開発を簡素化しますが、複雑なインタラクションにはカスタムソリューションが依然として必要です。

キーボードナビゲーション、適切なARIA実装、明確な閉じるパターンに焦点を当ててください。ネイティブAPIを使用するかカスタムコンポーネントを構築するかに関わらず、アクセシビリティが実装の決定を主導する必要があります。これにより、インターフェースとのやり取り方法に関係なく、すべてのユーザーに対してポップオーバーが機能することを保証します。

FAQ

標準的な実装には、組み込みアクセシビリティを提供し、JavaScriptが不要なネイティブPopover APIを使用してください。複雑な位置決めロジックが必要な場合や、ネイティブAPIサポートがない古いブラウザをサポートする必要がある場合にのみ、Popper.jsなどのライブラリを選択してください。

ツールチップはホバー時に簡潔なテキストを表示し、自動的に消えるため、単純なARIAラベルのみが必要です。ポップオーバーはインタラクティブな要素を含むため、フォーカス管理、複数の閉じる方法、スクリーンリーダーが正しく読み上げるためのrole dialogやaria-modalを含む適切なARIA属性が必要です。

Popover APIは、Chrome 114+、Edge 114+、Safari 17+でサポートされています。Firefoxのサポートは開発中です。常に現在のブラウザ互換性を確認し、本番環境で実装する前に機能検出を使用してサポートされていないブラウザ用のフォールバックを提供してください。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay