【HTML】popover属性でモーダルウィンドウを作ると勝手にスクロールされる件の解決法【CSS】

気づけばブラウザの進化に置いていかれそうな今日この頃。
情報をアップデートしつつ試していたのですが、「popover属性」で不思議な挙動に遭遇しました。

「popover html css」などで検索して実装してみたものの、
なぜかモーダルを開くと一番下までスクロールした状態で表示されてしまう!」 という挙動が発生。

今回はそれについての備忘録です。
同じ落とし穴にハマっている方等のご参考になれれば幸いです。

popover属性とは?モダンなモーダル実装

popover属性は、ツールチップやトースト、そしてモーダルウィンドウのような見た目を、HTMLとCSSだけでシンプルに実装できる便利な機能です。
これまで基本的にJavaScriptが必要だったのが、標準機能としてBaseline(全ブラウザ対応)に乗ってきたのは嬉しいニュース。
2025年現在、主要ブラウザなら問題なく動作します。

基本的な使い方

使い方は非常にシンプル。
表示させたい要素にpopover属性を書き、ボタンにはpopovertarget属性でそのIDを指定するだけ。

<button popovertarget="modal">詳細を見る</button>

<div id="modal" popover>
  <p>ここにコンテンツが入ります。</p>
</div>

これだけで動きます。仕様が素晴らしくシンプルで有難い限り!

今回の課題:dialog要素+popoverでスクロールがおかしい

今回私が試したのは、dialog要素に popover属性をつける方法だったんですが、
ここで「モーダルウィンドウ内のスクロール位置がおかしい」という問題が発生しました。

発生した事象

ウィンドウの高さを超えるような流しコンテンツをモーダルウィンドウ内に入れた際、なぜか開いた瞬間にモーダルウィンドウの一番下が表示されてしまいました。

<button popovertarget="modal">詳細を見る</button>

<dialog id="modal" popover>
	<h3>【重要】利用規約</h3>
	<p>...長い文章...</p>
	<button popovertarget="modal" popovertargetaction="hide">Close</button>
</dialog>

<style>
#modal::backdrop {
	background: rgba(0,0,0,.3);
}
#modal:popover-open {
	border: none;
  	overflow-y: auto;
  	width: 80vw;
	max-width: 800px;
	max-height: 80vh;
 }
</style>  
イメージ図:最下部が表示されてしまう

モーダルウィンドウは一番下に「閉じるボタン」を設置するケースも多いと思うんですが、
ユーザーからすると、開いた瞬間に「閉じるボタン」がある最下部を見せられることになり、文章の最初に戻るためにスクロールし直さなければならない状態に…
これはとても使いづらい。

原因(仮説)

ChatGPTと相談しつつ検証してみたところ、

①モーダルウィンドウ内部の最後にフォーカス可能要素(今回だと「閉じる」ボタン)がある
かつ
②overflow: auto でスクロール領域になっている
場合、
そのフォーカス要素が見える位置まで強制的にスクロールしてしまう(その位置を初期表示位置としてしまう) という挙動になる

というブラウザの親切機能(?)の可能性が高そうでした。

対処法

上記仮説を元に試してみたところ、いくつか解消方法を見つけました。
しかし、簡単なやり方だとお勧めできない点もちらほら。

モーダルウィンドウ内の一番上の要素に tabindex=”1″ をつける
→タブキー移動の順序を乱すためアクセシビリティ的に望ましくない

モーダルウィンドウ内の一番下のボタンを削除する
→ユーザーの使いやすさを考えると消す訳にはいかない場合も

モーダルウィンドウ内の一番下のボタンボタンに tabindex=”-1″ をつけてフォーカスを外す
→こちらもアクセシビリティ的に望ましくない

dialog要素をやめてdiv要素にする
→セマンティックなマークアップを捨てるのはちょっと勿体ない

これらを踏まえ、こういった問題が出ない解決策がこちら。
「JSで開いた瞬間に一番上の見出しにフォーカスを当てる」という方法にすると、先ほどのような懸念点は解消されます。

※折角HTML&CSSで実装できるモーダルウィンドウにJSを追加することになるんですが…
 JSを避けたかったらさきほどの手っ取り早い解決策をご検討ください。

手順1:一番上の要素にIDとtabindexを付与

見出しなどに、JSで指定するためのIDと、プログラムからフォーカス可能にするための tabindex=”-1″ をつけます。

<button popovertarget="modal">詳細を見る</button>

<dialog id="modal" popover>
	<h3 id="modal-top" tabindex="-1">【重要】利用規約</h3>
	<p>...長い文章...</p>
	<button popovertarget="modal" popovertargetaction="hide">Close</button>
</dialog>

<style>
#modal::backdrop {
	background: rgba(0,0,0,.3);
}
#modal:popover-open {
	border: none;
  	overflow-y: auto;
  	width: 80vw;
	max-width: 800px;
	max-height: 80vh;
 }
</style>  

手順2:JavaScriptでフォーカス制御

toggle イベントを監視し、ポップオーバーが開いた時だけトップにフォーカスを移動させます。

<script>
	const modal = document.getElementById('modal');
	modal.addEventListener('toggle', () => {
		// ポップオーバーが開いている状態(:popover-open)なら
		if (modal.matches(':popover-open')) {
			document.getElementById('modal-top').focus();
		}
	});
</script>

これで解決。
モーダルを表示した際、スクロール地点は一番上の状態になり、かつキーボード操作などのアクセシビリティも損ないません。

イメージ図:一番上が表示されるように

まとめ

popover属性は非常に便利ですが、dialog要素で長文コンテンツを扱う際は「フォーカスによる初期表示位置の変更」に注意が必要です。

  • 事象:dialog要素 + popover属性で長文を表示+コンテンツ内最下部にボタンを設置すると、最下部が初期表示位置になってしまう
  • 原因:最下部のボタンにフォーカスが吸われるため
  • 解決策: コンテンツ最上部にtabindex=”-1″を付与し、JSで明示的にフォーカスを当てる
    ※JSを使いたくなかったら、記事中の手っ取り早い解決策(リスクあり)を検討!

結局JSか、みたいなところはちょっとありつつ、
これだけ実装が簡単だとスクロールが発生しない短いコンテンツのモーダルウィンドウなら問題なく使用できそうです。
モダンな実装を取り入れつつ、細かい挙動も丁寧に調整していきたいですね。

参考になれば幸いです!