CSS Anchor Positioning入門 — ツールチップとポップオーバーの新常識


CSS Anchor Positioningとは

CSS Anchor Positioningは、要素を別の要素(アンカー)に対して相対的に配置できる新しいCSS機能です。従来JavaScriptライブラリ(Popper.js、Floating UIなど)が担っていた役割をCSSだけで実現できます。

従来の課題

// Popper.jsを使った実装
import { createPopper } from '@popperjs/core'

const button = document.querySelector('#button')
const tooltip = document.querySelector('#tooltip')

createPopper(button, tooltip, {
  placement: 'top',
  modifiers: [
    { name: 'offset', options: { offset: [0, 8] } },
  ],
})

これがCSSだけで可能になります。

ブラウザサポート

2026年2月時点:

  • Chrome 125+(2024年5月〜)
  • Edge 125+
  • Safari: 未サポート(polyfill使用推奨)
  • Firefox: 未サポート(polyfill使用推奨)

PolyfillとしてOddbird CSS Anchor Positioningが利用できます。

基本的な使い方

アンカーとターゲットの設定

<button id="my-button">ボタン</button>
<div id="tooltip">ツールチップ</div>
/* アンカーを定義 */
#my-button {
  anchor-name: --my-anchor;
}

/* アンカーに対して配置 */
#tooltip {
  position: absolute;
  position-anchor: --my-anchor;

  /* アンカーの上部に配置 */
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% 0; /* 中央揃え */
}

anchor()関数

anchor()関数は、アンカー要素の位置を参照します。

.tooltip {
  /* アンカーの上に配置 */
  bottom: anchor(top);

  /* アンカーの下に配置 */
  top: anchor(bottom);

  /* アンカーの左に配置 */
  right: anchor(left);

  /* アンカーの右に配置 */
  left: anchor(right);

  /* アンカーの中央 */
  left: anchor(center);
  top: anchor(center);
}

オフセット付き配置

.tooltip {
  /* アンカーの上、8px離す */
  bottom: calc(anchor(top) + 8px);

  /* アンカーの右、16px離す */
  left: calc(anchor(right) + 16px);
}

position-area — 簡単な配置

position-areaを使うと、より簡潔に配置を指定できます。

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;

  /* アンカーの上、中央 */
  position-area: top;

  /* アンカーの下、左 */
  position-area: bottom left;

  /* アンカーの右、中央 */
  position-area: right;
}

position-areaの値

   top-left    |   top    |   top-right
  -------------+----------+-------------
   left        | (anchor) |   right
  -------------+----------+-------------
   bottom-left |  bottom  | bottom-right
/* 例 */
.tooltip-top { position-area: top; }
.tooltip-bottom { position-area: bottom; }
.tooltip-left { position-area: left; }
.tooltip-right { position-area: right; }
.tooltip-top-left { position-area: top left; }
.tooltip-bottom-right { position-area: bottom right; }

実践例: ツールチップ

シンプルなツールチップ

<button class="anchor-button">ホバーしてね</button>
<div class="tooltip">これはツールチップです</div>
.anchor-button {
  anchor-name: --button-anchor;
}

.tooltip {
  position: absolute;
  position-anchor: --button-anchor;
  position-area: top;

  margin-bottom: 8px;
  padding: 8px 12px;
  background: #333;
  color: white;
  border-radius: 4px;
  font-size: 14px;
  white-space: nowrap;

  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}

.anchor-button:hover + .tooltip {
  opacity: 1;
}

/* 矢印 */
.tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  translate: -50% 0;
  border: 6px solid transparent;
  border-top-color: #333;
}

@position-try — フォールバック配置

画面端で要素がはみ出る場合の自動調整。

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;
  position-area: top;

  /* フォールバック */
  position-try-fallbacks:
    --bottom,
    --left,
    --right;
}

@position-try --bottom {
  position-area: bottom;
}

@position-try --left {
  position-area: left;
}

@position-try --right {
  position-area: right;
}

position-try-order

.tooltip {
  position-try-fallbacks:
    --bottom,
    --left,
    --right;

  /* より多くのスペースがある位置を優先 */
  position-try-order: most-block-size;
}

Popover APIとの統合

Popover APIと組み合わせると、さらに強力です。

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

<div popover id="my-popover" class="popover">
  <h3>ポップオーバー</h3>
  <p>これはポップオーバーの内容です。</p>
</div>
.trigger {
  anchor-name: --trigger-anchor;
}

.popover {
  position-anchor: --trigger-anchor;
  position-area: bottom;
  margin-top: 8px;

  /* Popoverのデフォルトスタイルをリセット */
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);

  /* アニメーション */
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.2s, transform 0.2s, overlay 0.2s allow-discrete, display 0.2s allow-discrete;
}

.popover:popover-open {
  opacity: 1;
  transform: translateY(0);
}

/* 開始状態をアニメーション */
@starting-style {
  .popover:popover-open {
    opacity: 0;
    transform: translateY(-10px);
  }
}

実践例: ドロップダウンメニュー

<button popovertarget="menu" class="menu-button">メニュー</button>

<div popover id="menu" class="dropdown-menu">
  <button>項目 1</button>
  <button>項目 2</button>
  <button>項目 3</button>
</div>
.menu-button {
  anchor-name: --menu-anchor;
}

.dropdown-menu {
  position-anchor: --menu-anchor;
  position-area: bottom left;
  margin-top: 4px;

  min-width: anchor-size(width);
  padding: 4px;
  background: white;
  border: 1px solid #ddd;
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dropdown-menu button {
  padding: 8px 12px;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
  border-radius: 4px;
}

.dropdown-menu button:hover {
  background: #f0f0f0;
}

anchor-size() — アンカーのサイズを参照

.tooltip {
  /* アンカーと同じ幅 */
  width: anchor-size(width);

  /* アンカーの幅の2倍 */
  width: calc(anchor-size(width) * 2);

  /* アンカーと同じ高さ */
  height: anchor-size(height);
}

実践例: コンテキストメニュー

<div class="content" id="content">
  右クリックしてみて
</div>

<div popover id="context-menu" class="context-menu">
  <button>コピー</button>
  <button>貼り付け</button>
  <button>削除</button>
</div>
const content = document.getElementById('content')
const menu = document.getElementById('context-menu')

content.addEventListener('contextmenu', (e) => {
  e.preventDefault()

  // 動的アンカー位置
  content.style.anchorName = '--context-anchor'

  // カーソル位置に仮想アンカー
  const rect = content.getBoundingClientRect()
  content.style.setProperty('--anchor-x', `${e.clientX - rect.left}px`)
  content.style.setProperty('--anchor-y', `${e.clientY - rect.top}px`)

  menu.showPopover()
})
.context-menu {
  position: absolute;
  position-anchor: --context-anchor;

  /* カーソル位置に配置 */
  left: anchor(left);
  top: anchor(top);
  translate: var(--anchor-x, 0) var(--anchor-y, 0);

  padding: 4px;
  background: white;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

Polyfillの使用

npm install @oddbird/css-anchor-positioning
import { polyfill } from '@oddbird/css-anchor-positioning'

// ブラウザがサポートしていない場合のみpolyfill
if (!CSS.supports('anchor-name', '--test')) {
  polyfill()
}

または、CDN:

<script type="module">
  import { polyfill } from 'https://cdn.jsdelivr.net/npm/@oddbird/css-anchor-positioning'

  if (!CSS.supports('anchor-name', '--test')) {
    polyfill()
  }
</script>

パフォーマンスとベストプラクティス

1. アンカーを明示的に指定

/* ✅ 推奨 */
.tooltip {
  position-anchor: --my-anchor;
}

/* ❌ 非推奨(暗黙的な関連) */

2. position-areaを優先

/* ✅ シンプル */
.tooltip {
  position-area: top;
}

/* ❌ 冗長 */
.tooltip {
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% 0;
}

3. フォールバックを設定

.tooltip {
  position-try-fallbacks: --bottom, --left, --right;
}

まとめ

CSS Anchor Positioningは、以下のメリットがあります。

  • JavaScriptライブラリ不要: Popper.js、Floating UIが不要
  • パフォーマンス: CSSネイティブで高速
  • 宣言的: HTMLとCSSのみで完結
  • Popover API連携: モダンなUIパターンを簡単に実装

現時点ではpolyfillが必要ですが、今後ブラウザサポートが広がれば、ツールチップやドロップダウンメニューの実装が大幅に簡素化されます。

参考リンク