Popover
ネイティブの Popover API を使った、操作可能な非モーダル UI。フォーカス管理、ARIA 属性の関連付け、トリガーを基準にした配置に対応します。
<pe-popover data-popover-offset="8">
<button type="button" class="Trigger" data-popover-trigger>Account</button>
<div class="Popup" data-popover-content hidden>
<span class="Arrow" data-popover-arrow></span>
<div class="Header">
<h2 class="Title" data-popover-title>Account</h2>
<button type="button" class="Close" data-popover-close>Close</button>
</div>
<p class="Description" data-popover-description>Manage your current session.</p>
</div>
</pe-popover>.Trigger {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
pe-popover {
display: contents;
}
.Popup {
width: min(20rem, calc(100vw - 1rem));
padding: 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
overflow: visible;
background: oklch(97% 0 0);
color: oklch(14.5% 0 0);
box-shadow: 0 0.75rem 2rem oklch(0% 0 0 / 18%);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
color: white;
box-shadow: 0 0.75rem 2rem oklch(0% 0 0 / 35%);
}
&.Nested {
width: min(18rem, calc(100vw - 1rem));
}
}
.Popup .Header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.Popup .Title {
margin: 0;
font-size: 1rem;
font-weight: 500;
line-height: 1.25rem;
}
.Popup .Description {
margin: 0.5rem 0 0.75rem;
line-height: 1.5;
}
.Popup .Close {
min-height: 2rem;
padding: 0 0.5rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
}
.Popup .Arrow {
display: block;
width: 12px;
height: 6px;
overflow: clip;
pointer-events: none;
&::before {
position: absolute;
bottom: 0;
left: 50%;
box-sizing: border-box;
width: calc(6px * sqrt(2));
height: calc(6px * sqrt(2));
content: "";
transform: translate(-50%, 50%) rotate(45deg);
border: 1px solid oklch(14.5% 0 0);
background: oklch(97% 0 0);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
}
}
&[data-side="top"] {
bottom: -6px;
rotate: 180deg;
}
&[data-side="bottom"] {
top: -6px;
rotate: 0deg;
}
&[data-side="left"] {
right: -9px;
rotate: 90deg;
}
&[data-side="right"] {
left: -9px;
rotate: -90deg;
}
}
Popover は操作可能な非モーダルコンテンツに使います。短いヒントには Tooltip、モーダルタスクには Dialog を使ってください。コンテンツには hidden とアクセシブル名が必要です。
| パーツ | セレクター | 役割 |
|---|---|---|
| ホスト | <pe-popover> |
1 つのポップオーバーの状態とライフサイクルを管理します |
| トリガー | [data-popover-trigger] |
ポップオーバーを開閉します |
| コンテンツ | [data-popover-content] |
role="dialog" を持つ、popover="manual" の要素 |
| 矢印 | [data-popover-arrow] |
任意の装飾要素。コンテンツの直下に置き、計算後の位置を受け取ります |
| タイトル | [data-popover-title] |
アクセシブルな名前として使う要素 |
| 説明 | [data-popover-description] |
アクセシブルな説明として使う要素 |
| 閉じる | [data-popover-close] |
ポップオーバーを閉じます |
外部トリガー
Section titled “外部トリガー”<button type="button" data-popover-trigger="filters-popover">Filters</button>
<pe-popover id="filters-popover"> <div data-popover-content hidden aria-label="Filters"> ... </div></pe-popover>初期フォーカス
Section titled “初期フォーカス”最初のフォーカス対象を変更するには data-popover-initial-focus を指定します。
ネストしたポップオーバー
Section titled “ネストしたポップオーバー”子のホスト要素は、親のコンテンツ内に置いてください。Escape キーや外側のクリックでは、親より先に子のポップオーバーが閉じます。
<pe-popover>
<button type="button" class="Trigger" data-popover-trigger>Open parent</button>
<div class="Popup" data-popover-content hidden>
<div class="Header">
<h2 class="Title" data-popover-title>Parent popover</h2>
<button type="button" class="Close" data-popover-close>Close parent</button>
</div>
<pe-popover data-popover-side="right" data-popover-offset="8">
<button type="button" class="Trigger" data-popover-trigger>Open child</button>
<div class="Popup Nested" data-popover-content hidden>
<div class="Header">
<h2 class="Title" data-popover-title>Child popover</h2>
<button type="button" class="Close" data-popover-close>Close child</button>
</div>
</div>
</pe-popover>
</div>
</pe-popover>.Trigger {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
pe-popover {
display: contents;
}
.Popup {
width: min(20rem, calc(100vw - 1rem));
padding: 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
overflow: visible;
background: oklch(97% 0 0);
color: oklch(14.5% 0 0);
box-shadow: 0 0.75rem 2rem oklch(0% 0 0 / 18%);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
color: white;
box-shadow: 0 0.75rem 2rem oklch(0% 0 0 / 35%);
}
&.Nested {
width: min(18rem, calc(100vw - 1rem));
}
}
.Popup .Header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.Popup .Title {
margin: 0;
font-size: 1rem;
font-weight: 500;
line-height: 1.25rem;
}
.Popup .Description {
margin: 0.5rem 0 0.75rem;
line-height: 1.5;
}
.Popup .Close {
min-height: 2rem;
padding: 0 0.5rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
}
.Popup .Arrow {
display: block;
width: 12px;
height: 6px;
overflow: clip;
pointer-events: none;
&::before {
position: absolute;
bottom: 0;
left: 50%;
box-sizing: border-box;
width: calc(6px * sqrt(2));
height: calc(6px * sqrt(2));
content: "";
transform: translate(-50%, 50%) rotate(45deg);
border: 1px solid oklch(14.5% 0 0);
background: oklch(97% 0 0);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
}
}
&[data-side="top"] {
bottom: -6px;
rotate: 180deg;
}
&[data-side="bottom"] {
top: -6px;
rotate: 0deg;
}
&[data-side="left"] {
right: -9px;
rotate: 90deg;
}
&[data-side="right"] {
left: -9px;
rotate: -90deg;
}
}
API リファレンス
Section titled “API リファレンス”| 名前 | 説明 |
|---|---|
data-popover-trigger | ポップオーバーを開閉する要素に指定します。値が空なら同じホスト内、ホストの id を指定した場合は対応する外部ポップオーバーが対象です。 |
data-popover-content | 表示するコンテンツ要素です。`popover="manual"` と、配置に必要なスタイルが設定されます。 |
data-popover-arrow | 任意の装飾用矢印です。data-popover-content の直下に置く必要があります。 |
data-popover-close | コンテンツ内の要素に指定すると、操作時にポップオーバーを閉じます。 |
data-popover-title | コンテンツの `aria-labelledby` で参照するタイトル要素です。 |
data-popover-description | コンテンツの `aria-describedby` で参照する説明要素です。 |
data-popover-initial-focus | 開いたときに最初にフォーカスする要素です。指定がなければ、最初のフォーカス可能な要素が使われます。 |
ホスト API
Section titled “ホスト API”| 名前 | 型 | 説明 |
|---|---|---|
isOpen | boolean (readonly) | ポップオーバーが現在開いているかどうかを読み取ります。 |
open(trigger?) | void | トリガーに対してポップオーバーを開き、配置します。 |
close() | void | プログラムからポップオーバーを閉じます。 |
toggle(trigger?) | void | 開く、閉じる、またはアクティブなトリガーを変更します。 |
| 名前 | 説明 |
|---|---|
data-popover-side | 優先する表示方向: top | right | bottom | left。初期値: bottom。 |
data-popover-align | トリガーに対する揃え方: start | center | end。初期値: center。 |
data-popover-offset | トリガーからの距離(ピクセル)。初期値: 0。 |
data-popover-align-offset | 表示方向と直交する軸のずれ(ピクセル)。初期値: 0。 |
data-popover-collision-padding | 表示方向の反転や位置調整で確保する、ビューポート端からの余白。初期値: 5。 |
data-popover-arrow-padding | 矢印とコンテンツ端との最小距離。初期値: 5。 |
data-state | ホスト、コンテンツ、矢印、トリガーに "open" または "closed" を設定します。トリガーが複数ある場合、実際に開いたトリガーだけが "open" になります。 |
data-side / data-align | 画面端を考慮して計算された、実際の表示方向と揃え方です。コンテンツと矢印に設定されます。 |
data-uncentered | 端の余白によりトリガー上で中央に配置できない場合、矢印に付与されます。 |
| 名前 | 説明 |
|---|---|
pe-popover:open | 開いたときに発火します。detail には content、trigger、reason が含まれます。 |
pe-popover:close | 閉じたときに発火します。detail には content、trigger、reason が含まれます。 |
閉じる理由: trigger、close-control、dismiss、escape、programmatic、native。
スタイリング
Section titled “スタイリング”コンテンツはトップレイヤーに表示され、固定位置の座標だけがインライン設定されます。見た目は利用側で指定します。
.Popup { width: min(20rem, calc(100vw - 1rem)); border: 1px solid oklch(14.5% 0 0); border-radius: 0; overflow: visible; background: oklch(97% 0 0);}任意の [data-popover-arrow] は装飾専用としてコンテンツ直下に置きます。サイズ、形、方向別のスタイルは CSS で指定し、そのサイズを data-popover-offset に含めてください。
開いている間は、コンテンツ要素で次の CSS 変数も利用できます。
| 名前 | 説明 |
|---|---|
--anchor-width / --anchor-height |
アクティブなトリガーの寸法 |
--available-width / --available-height |
実際の表示方向で利用できるビューポート内の領域 |
--positioner-width / --positioner-height |
現在のコンテンツ寸法(リサイズ時に更新) |
--transform-origin |
表示・非表示アニメーションに使える、トリガーを基準にした原点 |
表示・非表示時には data-starting-style と data-ending-style も利用できます。