CSS Anchor PositioningとPopover APIで「位置計算地獄」から解放される話 2026年版

Web技術

ドロップダウン。ツールチップ。サブメニュー。ユーザーアイコンを押したら出てくるアレ。

UIの現場では毎日のように出番があるのに、実装はなぜか毎回しんどい。クリックで開閉して、Escで閉じて、外側クリックでも閉じて、フォーカスも良い感じにして、さらに位置はボタンの下。スクロールしたら追従。画面端なら反転。はい、今日も位置計算で一日が終わる。

ここに新しい選択肢が入ってきました。Popover APIとCSS Anchor Positioningです。組み合わせると、HTMLとCSSだけで「開閉」と「位置決め」をかなりの範囲まで片付けられます。2026年1月のFirefox 147対応で、Anchor Positioningが主要ブラウザに揃ったという流れもあり、現場投入を検討しやすくなりました。

まず何がうれしいのか ざっくり3行

Popover APIで、開閉とフォーカスの面倒をブラウザがやってくれる。

Anchor Positioningで、基準要素に対してCSSだけで位置を決められる。

さらにフォールバック指定で、画面端ではみ出さない動きまで狙える。

なぜ今までつらかったのか 位置計算という名の沼

従来のポップオーバー実装は、だいたい同じ苦労を踏みます。

  • 開閉イベントの管理が増える。クリック、Esc、外側クリック。
  • フォーカス制御が厄介。アクセシビリティ対応で急に難易度が上がる。
  • 位置決めが地獄。getBoundingClientRect、スクロール、リサイズ、ズレ、はみ出し。

「この程度のUI、コピペで済むでしょ」と思っても、微妙に要件が違う。右寄せなのか、左寄せなのか。ヘッダー固定なのか。overflowの中なのか。気づいたら例外対応の寄せ集めになって、いつか自分が泣きます。

止める必要があるのは何か それは手動の位置計算

今回のテーマで止めたいのは「毎回自前で位置を計算して、top/leftを当て込むやり方」です。

もちろん全部を捨てられるわけではありません。けれど、標準機能で届く範囲は想像より広い。ここを標準に寄せるだけで、保守コストがごっそり落ちます。

あなたのプロジェクト、ポップオーバーのためだけに小さな位置計算関数が増殖していませんか。

Popover APIとは 開閉がHTML属性で済むやつ

Popover APIは、ポップオーバー要素を「popover」として宣言し、ボタン側から「popovertarget」で紐付ける仕組みです。開閉の基本動作をブラウザが持ってくれます。

最小構成のHTML

<button popovertarget="my-popover">開く</button>

<div id="my-popover" popover>
  <p>ポップオーバーの中身</p>
</div>

これだけで、クリックで開閉、Escで閉じる、外側クリックで閉じる、フォーカス管理、といったお作法を標準側が面倒見てくれます。

注意点もあります。popovertargetは仕様上、buttonやinput type=”button”での利用が前提になっており、a要素ではそのまま動かない場面があります。リンク見た目で押したいなら、buttonをリンク風にスタイルする方が事故が少ない。

popovertargetactionも覚えておくと便利

トグルだけでなく、showやhideを明示できます。連打や状態同期が絡むときに効きます。

<button popovertarget="my-popover" popovertargetaction="show">開く</button>
<button popovertarget="my-popover" popovertargetaction="hide">閉じる</button>

Popoverだけだと位置が決められない そこにAnchor Positioning

Popover APIは開閉を簡単にしてくれます。けれど「ボタンの右に出したい」「アイコンの下に出したい」みたいな位置指定は、従来だとJSで座標を計算しがちでした。

そこでCSS Anchor Positioningです。アンカーとなる要素を決めて、そこを基準に別要素を配置できます。つまり、位置計算の主役がJSからCSSに移る。

Anchor Positioningの考え方 アンカーに結びつける

ざっくり手順は3つです。

  • 基準要素に名前をつける(anchor-name)
  • ポップオーバー側でその名前を参照する(position-anchor)
  • 配置方法を指定する(anchor()関数、またはposition-area)

ステップ1 anchor-nameで基準を作る

.trigger {
  anchor-name: --trigger-anchor;
}

ここでの”–trigger-anchor”は任意の識別子です。CSS変数みたいな見た目ですが、アンカー名として使われます。

ステップ2 position-anchorで紐付ける

.menu {
  position-anchor: --trigger-anchor;
}

これで.menuは.triggerを基準にできる状態になります。

ステップ3 位置を決める 2つの流儀

流儀A anchor()関数で座標を取る

アンカー要素の上端、下端、左端、右端、中央などを参照できます。

.menu {
  position: absolute;
  top: anchor(bottom);
  left: anchor(left);
}

余白を足したいならcalcも普通に使えます。

.menu {
  position: absolute;
  top: calc(anchor(bottom) + 8px);
  left: anchor(center);
  transform: translateX(-50%);
}

流儀B position-areaでエリア指定する

block-endなら下、inline-endなら右、という感じで「どっち側に出すか」を宣言できます。書字方向も踏まえてくれるのが地味に良い。

.menu {
  position-anchor: --trigger-anchor;
  position-area: block-end;
}

右上に出したいなら組み合わせます。

.menu {
  position-anchor: --trigger-anchor;
  position-area: block-start inline-end;
}

実装例 HTMLとCSSだけでドロップダウンを作る

ここで一回、まとまった形を置きます。まずはシンプルに「ボタンの下に出るメニュー」です。

HTML

<div class="demo">
  <button class="trigger" popovertarget="user-menu">ユーザーメニュー</button>

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

CSS

.trigger {
  anchor-name: --user-trigger;
}

/* popoverはトップレイヤーに乗るので、positionの扱いが普段と少し違って見える場合があります */
.menu {
  position-anchor: --user-trigger;

  /* 分かりやすさ優先で下に出す */
  position-area: block-end;

  /* 見た目は適当に */
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 8px 24px rgba(0,0,0,.12);
}

.menu ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.menu li + li {
  margin-top: 8px;
}

.menu a {
  text-decoration: none;
}

JSゼロで、開閉と配置が成立します。スクロールやリサイズでズレたら自前で再計算、みたいな発想から距離を置けるのがポイント。

画面端でのはみ出し対策 position-try-fallbacksが効く

ポップオーバーは端っこで破綻しがちです。右端にいるのに右に出そうとして画面外へ、あるある。これをCSS側で「はみ出したら反転してね」と書けるのがposition-try-fallbacksです。

.menu {
  position-anchor: --user-trigger;
  position-area: block-end;
  position-try-fallbacks: flip-block;
}

上下だけでなく左右も反転できます。

.menu {
  position-anchor: --user-trigger;
  position-area: inline-end;
  position-try-fallbacks: flip-inline;
}

上下も左右も両方見たいなら並べます。

.menu {
  position-anchor: --user-trigger;
  position-area: block-end inline-end;
  position-try-fallbacks: flip-block flip-inline;
}

このあたり、今までならJSで分岐していた領域です。CSSで宣言できるのがうれしい。

メリット こういう現場で刺さる

  • 開閉ロジックとフォーカス管理を標準に寄せられる。アクセシビリティ対応の負担が下がる。
  • 位置計算が減る。スクロールやリサイズで再計算、という発想を薄められる。
  • ドロップダウンやサブメニューが、設計として単純になる。後から読む人が助かる。
  • 小さなUIのために巨大なライブラリを抱えないで済む場面が増える。

個人的には、保守で効きます。半年後の自分が「これ誰が作ったんだよ」と言わない確率が上がる。大事。

デメリット 夢だけ見ていると足を取られる

  • ブラウザ対応は揃ってきたとはいえ、社内端末や組み込みブラウザの事情で足踏みすることがある。
  • Popoverはトップレイヤーに乗るため、既存のz-index運用やレイアウト前提と噛み合わないケースが出る。
  • デザイン要件が特殊だと、結局JSが必要になる場面もある。アニメーションの細かい同期など。
  • 実装チーム内で知識差があると、レビューで止まりやすい。新機能あるある。

とはいえ、全部が完璧でないのはいつものことです。必要なところから小さく入れるのが現実的。

やり方のコツ 既存実装から移行するならここを見る

1 まずはPopoverだけ導入してJSを削る

位置決めは後回しで良いです。まず開閉だけ標準に寄せる。イベントリスナーが減るだけでも価値があります。

2 位置計算が辛い箇所からAnchor Positioningへ

ヘッダーのユーザーメニュー、カードの三点リーダー、タスクのサブメニュー。頻出で、かつ画面端問題が起きやすいところが狙い目。

3 フォールバックを早めに決める

ブラウザ未対応や古い環境が混ざるなら、段階的に対応します。

  • 未対応時は単純な位置固定にする
  • 未対応時は従来JSにフォールバックする
  • そもそも未対応ブラウザを対象外にする

どれが正しいかはプロダクト次第。あなたの現場、IEの亡霊がまだいますか。いますよね、たまに。

忙しい人向け 最小セットだけ覚える

  • button側: popovertargetで紐付ける
  • popover側: popover属性を付ける
  • 基準側: anchor-nameで名前を付ける
  • 追従側: position-anchorで参照する
  • 配置: position-areaかanchor()で指定する
  • はみ出し: position-try-fallbacksで反転

関連して役に立つ知識 ポップオーバー実装の地雷回避

クリックで閉じる動きとフォームの相性

ポップオーバーの中にフォームが入ると、入力中に外側クリック扱いで閉じてしまう事故が起きがちです。Popover APIの動作を理解した上で、UI設計として「閉じない」動作が必要かを先に決めるのが安全。

アクセシビリティは後付けしない

ポップオーバーはフォーカスの扱いが全て、と言ってもいい。標準機能の恩恵が大きい領域なので、カスタム実装で頑張るほど失点しやすい。ここは好みよりも勝率を取りたいところです。

既存のライブラリを全否定しない

Popper系のライブラリが不要になる場面は増えます。けれど、複雑な衝突回避や高度なレイアウト最適化が必要なら、成熟したライブラリがまだ強い。標準で足りるか、ライブラリが必要か。見極めが腕の見せどころ。

この話が好きなら 次に読むと楽しい作品たち

映画の話ではありません。実装の話です。UIの体験は細部で決まるので。

  • Dialog要素: モーダルの標準化で同じく救われる領域
  • details/summary: アコーディオンを標準に寄せると保守が軽い
  • inert属性: モーダルやポップオーバーと一緒に語ると強い
  • prefers-reduced-motion: アニメーションを盛りたい人ほど一度は向き合う

ポップオーバーは小物です。だけど、サイトの印象を左右する小物でもある。小物をラクに作れる環境が整ってきた。2026年はこの波に乗ると気持ちいいです。

(Visited 1 times, 1 visits today)
タイトルとURLをコピーしました