Docs
検索

Popover

クリックで開閉するポップオーバーを作成できるコンポーネントです。

Overview

Preview

ポップオーバーの中身です。外側のクリックやEscキーで閉じられます。

<Popover.Root popoverId="pop-01">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">Open Popover</Popover.Trigger>
<Popover.Popup max-w="20rem">
<p>ポップオーバーの中身です。外側のクリックやEscキーで閉じられます。</p>
</Popover.Popup>
</Popover.Root>

ブラウザ標準の Popover APIpopover属性とpopovertarget属性)だけで動作するため、クライアント側のJavaScriptは一切必要ありません。

初期値のtype="auto"では、以下の動作はすべてブラウザが行います。

  • トリガーのクリックによる開閉
  • 外側のクリックやEscキーで閉じる(light dismiss)
  • 閉じたあと、トリガーへフォーカスを戻す
  • トリガーのaria-expandedの切り替え

Tabキーでフォーカスがポップアップの外へ移動しても閉じません(Popover APIの仕様です)。フォーカスが外れたときに閉じたい場合は、focusoutイベントでhidePopover()を呼ぶなど、利用側で実装してください。

<Popover.Root>の中に<Popover.Trigger><Popover.Popup>を置くと、popovertargetidが自動で紐付きます。<Popover.Close>も同様です。

  • ポップアップは閉じている間、支援技術からも見えません。重要な操作や情報をポップアップの中だけに置かないでください。
  • <Popover.Trigger><Popover.Close>button要素である必要があります。popovertarget属性はbutton(およびinput type="button"など)でしか機能しません。
  • <Popover.Popup>にはroleを指定していません。フォームなど、まとまった操作を含む場合はrole="dialog"aria-labelを指定してください。

Styles

Popover のベーススタイルは、以下のCSSで定義されています。

_style.css
/*
 * ネイティブ Popover API(popover 属性)で開閉し、CSS Anchor Positioning で配置する。
 * anchor 配置は @supports で囲い、非対応ブラウザ(部分対応の Chrome 128〜130 を含む)には
 * UA 既定の中央配置(inset: 0; margin: auto)に戻してカードとして整えるだけにする。
 */
@layer lism-block {
  /* 調整用の変数は Root で受ける(Root の props がインラインに書く --popover-* をここで拾う) */
  .b--popover {
    /*
     * 矢印の縁は popup の -bd(--bdc / --bdw)を拾う。外側から継承した値で縁取りしないよう Root で未定義に戻す
     * (空値 `--bdc: ;` は未定義扱いにならず var() のフォールバックが効かないので initial を使う)。
     */
    --bdc: initial;
    --bdw: initial;
    --duration: var(--popover-duration, 0.15s);
    --offset: var(--popover-offset, var(--s5));
    --arrow-sz: var(--popover-arrow, 6px); /* 矢印の高さ(突出量)。0 で矢印なし */
    --popup-gap: calc(var(--offset) + var(--arrow-sz)); /* popup 本体とトリガーの間隔。offset は矢印の先とトリガーの間隔 */
    display: inline-block;
    anchor-scope: --popover, --popover-popup;
  }
  .b--popover_trigger {
    anchor-name: --popover;
  }

  .b--popover_popup {
    border: none;

    font-size: var(--fz--s);

    color: var(--text);
    background-color: var(--base);
    box-shadow: var(--bxsh--30);
    overflow: auto;
    padding: 0.75em 1em;
    opacity: 0;
    transition:
      opacity var(--duration),
      display var(--duration) allow-discrete,
      overlay var(--duration) allow-discrete;
  }
  .b--popover_popup:popover-open {
    opacity: 1;
  }
  @starting-style {
    .b--popover_popup:popover-open {
      opacity: 0;
    }
  }

  /*
   * anchor 配置。position-area は side と align の span を var() で合成し、
   * start / end は span-* で片側に伸ばして place-self でアンカー端に揃える。
   * span キーワードと揃える軸は side の系統(top / bottom と start / end)ごとに異なるので、
   * 系統側で --_spanStart / --_spanEnd / --_placeStart / --_placeEnd の候補を定義し、align 側はそれを選ぶだけにする。
   * top / bottom の align に span-x-* を使うのは、書字方向に従って RTL でも端に揃うため。
   * 物理(top/bottom)と論理(span-inline-*)の混在は文法上無効で宣言ごと落ちるので使わない。
   * side の start / end は inline 軸の論理方向(position-area の inline-start / inline-end)。block 軸は top / bottom の物理値のみ。
   */
  @supports (anchor-name: --a) and (anchor-scope: all) and (position-area: inline-start span-block-end) and (position-try-fallbacks: flip-block) {
    .b--popover_popup {
      --_span: span-all;
      anchor-name: --popover-popup; /* 矢印(::after)が popup の矩形を参照するため */
      position-anchor: --popover;
      position-area: var(--_side) var(--_span);
      inset: auto;
    }
    /*
     * ふきだしの矢印。ひし形の中心を「トリガー中心を popup の矩形にクランプした点」に置く。
     * flip 後の位置は CSS から知れない(@position-try でカスタムプロパティは設定できない)ので、
     * side / align / flip を見ずにトリガー側の辺へ寄るこの形にしている。
     * absolute だと popup が包含ブロックになりトリガーを anchor に取れないため fixed(overflow: auto にもクリップされない)。
     * スクロール追従は既定アンカー(position-anchor)にしか効かないので、トリガーを既定アンカーにして省略形の anchor() で参照する。
     *
     * 縁取りは 2 枚重ね。負の z-index は popup の背景・border より上に描かれるので、1 枚では内側半分が消せない。
     * ::before(縁色)を border box に、::after(背景色)を border 幅ぶん内側の padding box にクランプすると、
     * 外側の 2 辺だけ ::before がはみ出して縁になり、popup の border 線は ::after が上から消して口を開ける。
     * 影は付けない(同じ理由で内側半分の影が popup の上に見える)。
     */
    .b--popover_popup::before,
    .b--popover_popup::after {
      content: '';
      position: fixed;
      position-anchor: --popover;
      z-index: -1; /* 中身の下、popup の背景・影の上 */
      inline-size: calc(var(--arrow-sz) * 2);
      block-size: calc(var(--arrow-sz) * 2);
      clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
      translate: -50% -50%;
    }
    .b--popover_popup::before {
      background-color: var(--bdc, inherit);
      top: clamp(anchor(--popover-popup top), anchor(center), anchor(--popover-popup bottom));
      left: clamp(anchor(--popover-popup left), anchor(center), anchor(--popover-popup right));
    }
    .b--popover_popup::after {
      --_bdw: calc(var(--bdw, 0px) * 1.4142); /* 斜辺でも縁の太さが --bdw になるよう √2 倍ずらす */
      background-color: inherit;
      top: clamp(anchor(--popover-popup top) + var(--_bdw), anchor(center), anchor(--popover-popup bottom) - var(--_bdw));
      left: clamp(anchor(--popover-popup left) + var(--_bdw), anchor(center), anchor(--popover-popup right) - var(--_bdw));
    }
    /* --_place*: place-self の値(align-self justify-self)。揃える軸だけ start / end にする */
    .b--popover_popup:where([data-side='top'], [data-side='bottom']) {
      --_spanStart: span-x-end;
      --_spanEnd: span-x-start;
      --_placeStart: auto start;
      --_placeEnd: auto end;
      margin-block: var(--popup-gap);
      position-try-fallbacks:
        flip-block,
        flip-inline,
        flip-block flip-inline;
    }
    .b--popover_popup:where([data-side='start'], [data-side='end']) {
      --_spanStart: span-block-end;
      --_spanEnd: span-block-start;
      --_placeStart: start auto;
      --_placeEnd: end auto;
      margin-inline: var(--popup-gap);
      position-try-fallbacks:
        flip-inline,
        flip-block,
        flip-inline flip-block;
    }
    .b--popover_popup[data-side='top'] {
      --_side: top;
    }
    .b--popover_popup[data-side='bottom'] {
      --_side: bottom;
    }
    .b--popover_popup[data-side='start'] {
      --_side: inline-start;
    }
    .b--popover_popup[data-side='end'] {
      --_side: inline-end;
    }

    /* align: side 系統が用意した候補から選ぶ */
    .b--popover_popup:where([data-align='start']) {
      --_span: var(--_spanStart);
      place-self: var(--_placeStart);
    }
    .b--popover_popup:where([data-align='end']) {
      --_span: var(--_spanEnd);
      place-self: var(--_placeEnd);
    }
  }

  /* anchor 非対応: UA 既定の中央配置をカードとして整える */
  @supports not (
    (anchor-name: --a) and (anchor-scope: all) and (position-area: inline-start span-block-end) and (position-try-fallbacks: flip-block)
  ) {
    .b--popover_popup {
      margin: auto; /* lism-css の reset(*:not(dialog) { margin: 0 })で消える UA 既定値を戻して中央配置にする */
      max-width: calc(100vw - var(--s20) * 2);
      max-height: calc(100vh - var(--s20) * 2);
    }
    .b--popover_popup::backdrop {
      background-color: rgb(0 0 0 / 0.2);
    }
  }

  /* 減速設定時はフェードを無効化。インラインの --duration 指定よりも優先させるため !important */
  @media (prefers-reduced-motion: reduce) {
    .b--popover {
      --duration: 0s !important;
    }
  }
}

ソースコード全体は GitHub で公開しています。

How to use

@lism-css/ui パッケージでPopoverとして提供しています。

Import

import { Popover } from '@lism-css/ui/react';

<Popover.Root>, <Popover.Trigger>, <Popover.Popup>, <Popover.Close>が利用できます。

Props

プロパティ 説明
<Popover.Root>
popoverId
<Popover.Trigger> / <Popover.Close>popovertargetと、<Popover.Popup>idに使われるIDを指定します。未指定の場合は自動生成されたIDが使われます。
<Popover.Root>
offset
トリガーとポップアップの間隔を指定します。インラインスタイルの--popover-offsetとして出力されます。
<Popover.Trigger>
popoverId
popovertargetの値として出力されます。<Popover.Root>の外で単体利用するときにだけ指定します。
<Popover.Popup>
id
id属性として出力されます。<Popover.Root>の外で単体利用するときにだけ指定します。
<Popover.Popup>
side
ポップアップを表示する向きを指定します。data-side属性として出力されます。指定できる値はtop / bottom / start / endで、初期値はbottomです。start / endは横方向の向きで、書字方向に追従します(dir="ltr"ではstartが左、endが右です)。
<Popover.Popup>
align
ポップアップの揃え位置を指定します。sidetop / bottomのときは横方向、それ以外のときは縦方向の揃えです。data-align属性として出力されます。指定できる値はstart / center / endで、初期値はcenterです。
<Popover.Popup>
type
popover属性の値として出力されます。指定できる値はauto / manualで、初期値はautoです。manualにすると、外側のクリックやEscキーで閉じる動作(light dismiss)が無効になり、<Popover.Close>などのpopovertargetを持つボタンでしか閉じられなくなります。
<Popover.Close>
popoverId
popovertargetの値として出力されます。<Popover.Root>の外で単体利用するときにだけ指定します。
<Popover.Close>
icon
子要素を渡さない場合に表示されるアイコンを指定します。(初期値: x
<Popover.Close>
srText
アイコン表示時にスクリーンリーダー向けに出力されるテキストを指定します。初期値はCloseのため、日本語サイトではsrText="閉じる"の指定を推奨します。

<Popover.Root>の配下では、<Popover.Trigger> / <Popover.Popup> / <Popover.Close>に個別のIDを指定しないでください。IDを明示したい場合は<Popover.Root>popoverIdだけを使います。

子要素側のpopoverId / idは、<Popover.Root>を使わずにパーツを離れた場所へ配置する(各パーツに同じIDを手動で渡す)ための手段です。<Popover.Root>の配下で一部の子にだけ指定すると、紐付けが壊れてしまいます。

CSS変数

見た目の調整には、以下のCSS変数が使えます。いずれも<Popover.Root>.b--popover)で受け取るため、<Popover.Root>またはその祖先要素に指定してください。<Popover.Popup>に指定しても効きません。

変数 初期値 説明
--popover-offset var(--s5) トリガーとポップアップの間隔。
--popover-duration 0.15s 開閉時のフェードにかかる時間。prefers-reduced-motion: reduceの環境では0sになります。

ポップアップの初期スタイルは、背景色var(--base)・余白1emに影を付けたカードです。角丸は初期状態では付いていません。配色や余白、角丸は、Lismのプロパティ(bgc, c, p, bdrs, bxshなど)で上書きしてください。

Examples

表示位置を変える

<Popover.Popup>sideで表示する向きを、alignで揃え位置を指定できます。sidetop / bottomのときは横方向、それ以外のときは縦方向の揃えになります。

sidealignの指定例

side=“bottom” align=“start”

side=“bottom” align=“end”

side=“top” align=“center”

side=“end” align=“start”

<Cluster g="15">
<Popover.Root popoverId="pop-02">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">bottom / start</Popover.Trigger>
<Popover.Popup side="bottom" align="start" max-w="16rem">
<p>side="bottom" align="start"</p>
</Popover.Popup>
</Popover.Root>
<Popover.Root popoverId="pop-03">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">bottom / end</Popover.Trigger>
<Popover.Popup side="bottom" align="end" max-w="16rem">
<p>side="bottom" align="end"</p>
</Popover.Popup>
</Popover.Root>
<Popover.Root popoverId="pop-04">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">top / center</Popover.Trigger>
<Popover.Popup side="top" max-w="16rem">
<p>side="top" align="center"</p>
</Popover.Popup>
</Popover.Root>
<Popover.Root popoverId="pop-05">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">end / start</Popover.Trigger>
<Popover.Popup side="end" align="start" max-w="16rem">
<p>side="end" align="start"</p>
</Popover.Popup>
</Popover.Root>
</Cluster>

sidestart / endは書字方向に追従する論理的な向きです。dir="ltr"ではstartが左、endが右になり、dir="rtl"の環境では左右が入れ替わります。alignstart / endも、sidetop / bottomのときは同じように書字方向へ追従します。

また、画面の端に収まらないときは自動的に反対側へ反転します。

スタイルを変える

ポップアップの配色やborderは、Lismのプロパティで上書きできます。ふきだしの矢印は背景色に追従し、bdでborderを付けると縁の色と太さ(--bdc / --bdw)も矢印に反映されます。

配色とborderの指定例

bgc=“text” c=“base”

bd bdw=“2px” bdc=“current”

<Cluster g="15">
<Popover.Root popoverId="pop-07">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">配色を変える</Popover.Trigger>
<Popover.Popup bgc="text" c="base" max-w="16rem">
<p>bgc="text" c="base"</p>
</Popover.Popup>
</Popover.Root>
<Popover.Root popoverId="pop-08">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">borderを付ける</Popover.Trigger>
<Popover.Popup bd bdw="2px" bdc="current" max-w="16rem">
<p>bd bdw="2px" bdc="current"</p>
</Popover.Popup>
</Popover.Root>
</Cluster>

閉じるボタンを置く(type="manual"

type="manual"を指定すると light dismiss が無効になり、外側のクリックやEscキーでは閉じられなくなります。必ず<Popover.Close>を配置してください。

type='manual'
お知らせ

閉じるボタンを押すまで開いたままになります。

<Popover.Root popoverId="pop-06">
<Popover.Trigger bd px="15" py="5" bdrs="10" hov="-bgc">お知らせを見る</Popover.Trigger>
<Popover.Popup type="manual" max-w="20rem" bd>
<Stack g="5">
<Flex ai="center" jc="between" g="15">
<span className="-fw:bold">お知らせ</span>
<Popover.Close srText="閉じる" fz="l" hov="-c" />
</Flex>
<p>閉じるボタンを押すまで開いたままになります。</p>
</Stack>
</Popover.Popup>
</Popover.Root>

ブラウザ対応

ポップアップの配置には CSS Anchor Positioning を使用しています。

未対応のブラウザ(Firefox ESR 140 など)では、ブラウザ既定の配置にフォールバックし、ポップアップは画面中央に表示されます。この場合、side / align / offset による位置調整は適用されません。

開閉・light dismiss・フォーカス制御は Popover API の機能なので、フォールバック時もそのまま動作します。

© 2026 Lism CSS.