ポップオーバー
押したコントロールの横に開く小さなサーフェスに、読んだり操作したりできるコンテンツを表示します。コントロールに付いて動き、ページのほかの部分を暗くしたり、ふさいだりしません。コンパクトなウィンドウでは、ボトムシートの見た目をしたモーダルなサーフェスに表示します。
- カテゴリ
- コンテインメント
- トークン
comp.popover- レベル
- 基礎
- Web
tanrei-popoverschedule - SwiftUI
tanreiPopoverradio_button_unchecked - Jetpack Compose
TanreiPopoverradio_button_unchecked - Flutter
TanreiPopoverradio_button_unchecked - React Native
Popoverradio_button_unchecked
生成元 docs/status/status.json, specs/popover.spec.json
ボタンを押すと開きます:リンクのある説明と、アクションのある短いフォーム
ポップオーバーは、それを開いたコントロールの横でユーザーが操作するコンテンツに使います。いくつかの設定、短いフォーム、共有のパネル、リンクやボタンが必要な説明などです。開いている間も、ページのほかの部分はそのまま使えます。コンパクトなウィンドウでは、代わりにウィンドウの下端にボトムシートの見た目で表示します。Tanrei のポップオーバーとダイアログは、コンパクトなウィンドウではすべてそうなります。
コントロールは利用者側で用意します。Web では、ポップオーバーを横に置く要素の id を anchor に指定します。この要素は、ポップオーバーを開く要素(オープナー)でもあります。ポップオーバーは、この要素がダイアログを開くことと、そのダイアログが開いているかどうかを支援技術に伝え、閉じるときにはこの要素にフォーカスを戻します。フィールドがサーフェスのアンカーで、それを開くのがフィールドの中のボタンである場合は、そのボタンを opener に指定します。
ポップオーバーは、メニューと同じく制御コンポーネントです。オープナーが押されたら open を設定し、ポップオーバーが close を発火したら false に戻します。ホバーやフォーカス、時間の経過で、ひとりでに開くことはありません。close は理由を伝えます。escape、outside、focus-out、close(閉じるボタン)、anchor-hidden のいずれかです。アクションを押してもポップオーバーは閉じません。閉じるのは、利用者側のハンドラーです。コンパクトなウィンドウでシートとして表示されているときは、スクリムを押すと outside を伝え、focus-out と anchor-hidden は伝えません。
ポップオーバーには名前を付けます。見出しを描くなら headline、描かないなら label です。
SwiftUI、Compose、Flutter、React Native は計画中です。このページの冒頭で各プラットフォームに添えた名前は仕様が定めたもので、まだ提供していません。
<tanrei-button id="share" variant="outlined">共有</tanrei-button>
<!-- anchor は、ポップオーバーを横に置くコントロールの id です。 -->
<tanrei-popover id="share-panel" anchor="share" headline="このページを共有" closable close-label="共有を閉じる">
リンクを知っている全員が閲覧できます。
<tanrei-button slot="actions" dismissive>共有を停止</tanrei-button>
<tanrei-button slot="actions" confirming>リンクをコピー</tanrei-button>
</tanrei-popover>
<!-- フィールドの下のサーフェス:フィールドと同じ幅で、フォーカスはフィールドに残ります。 -->
<tanrei-text-field id="city" label="市区町村"></tanrei-text-field>
<tanrei-popover anchor="city" label="候補" width="anchor" initial-focus="anchor" no-inset>…</tanrei-popover>
<script type="module">
import '@tanrei/web/components/popover';
const opener = document.querySelector('#share');
const panel = document.querySelector('#share-panel');
// オープナーで開閉します。オープナーを押しても「外側」を押したことにはならないので、1 回押しただけで閉じてまた開くことはありません。
opener.addEventListener('click', () => (panel.open = !panel.open));
// Escape、外側を押す、フォーカスが離れる、閉じるボタン、アンカーが見えなくなる。
panel.addEventListener('close', (e) => {
console.log(e.detail.reason);
panel.open = false;
});
// アクションを押しても閉じません。処理が終わったら、ハンドラーが閉じます。
panel.querySelector('[confirming]').addEventListener('click', async () => {
await navigator.clipboard.writeText(location.href);
panel.open = false;
});
</script>import { useState } from 'react';
import { Button, Popover } from '@tanrei/react';
const [open, setOpen] = useState(false);
<Button id="share" variant="outlined" onClick={() => setOpen(!open)}>共有</Button>
<Popover anchor="share" open={open} headline="このページを共有" closable closeLabel="共有を閉じる" onClose={() => setOpen(false)}>
リンクを知っている全員が閲覧できます。
<Button slot="actions" dismissive onClick={stopSharing}>共有を停止</Button>
<Button slot="actions" confirming onClick={copyLink}>リンクをコピー</Button>
</Popover>配置、矢印、アンカーと同じ幅のポップオーバー
placement は、ポップオーバーをアンカーのどの側に置くかと、その辺に沿ってどうそろえるかを指定します。13 個の値はツールチップと同じで、意味も同じです。
| 値 | 位置 |
|---|---|
bottom-start(既定)、bottom、bottom-end |
アンカーの下。先頭側の端をそろえる、中央にそろえる、末尾側の端をそろえる |
top-start、top、top-end |
アンカーの上。そろえ方は同じ 3 通り |
start、start-top、start-bottom |
アンカーの先頭側の横。中央にそろえる、上端をそろえる、下端をそろえる |
end、end-top、end-bottom |
アンカーの末尾側の横。そろえ方は同じ 3 通り |
auto |
下、上、末尾側、先頭側の順に見て、最初に余裕のある側 |
先頭側と末尾側は、レイアウトの方向に従います。offset は、アンカーまでの距離をプラットフォームのレイアウト単位で指定します。省略すると comp.popover.offset になります。crossOffset は、置かれた辺に沿ってポップオーバーを動かします。アンカーのその辺から外れるところまでは動きません。
ポップオーバーは、ウィンドウの端から comp.popover.window-margin だけ内側に収まるように、次のことをこの順に行います。
- 指定された側に余裕がなければ、そろえ方を保ったまま反対側に回ります。
- どちらの側にも余裕がなければ、余裕の大きいほうの側に置かれ、その余裕に収まる大きさになります。このとき、コンテンツはスクロールします。
- 余白を保つのに必要なだけ、置かれた辺に沿ってずれます。
- ウィンドウがポップオーバーの最小の大きさより小さいときは、ウィンドウの外に出る代わりに、アンカーに重なります。
アンカーが動くと、ポップオーバーも付いていきます。いまの側に余裕がある間は、その側にとどまります。アンカーがまったく見えなくなると、理由 anchor-hidden で close を発火します。
ポップオーバーの幅はコンテンツの幅で、comp.popover.container.min-width(160)から container.max-width(400)の間に収まります。width="anchor" を指定すると、アンカーと同じ幅になります。フィールドの下に開くサーフェスに使います。どの場合も、ウィンドウの幅から余白を引いた幅を超えることはありません。
高さはパーツの高さの合計で、container.max-height(480)と、置かれた側の余裕が上限です。それを超えると、ヘッダーとアクションはその場に残り、その間でコンテンツがスクロールします。コンテンツがスクロールする間は、ヘッダーの下とアクションの上に区切り線を引きます。dividerVisibility を scroll(Web では divider-visibility)にすると、ヘッダーの下の線はコンテンツがスクロールされてから、アクションの上の線は下にまだ続きがある間だけ引かれます。cupertino と m3-like は、すべてのポップオーバーでこちらを選んでいます。SwiftUI、Compose、Flutter、React Native は、追従するまでこの描き方です。
コンテンツが自分で余白を持つ場合や、端まで届く場合(リストなど)は、inset を false にします(Web では no-inset)。
| パーツ | 内容 |
|---|---|
| ヘッダー | 見出しと、closable が true のときの閉じるボタン。コンテンツがスクロールしても動きません |
| コンテンツ | 既定のスロット。リンク、フィールド、ほかのコントロールなど、何でも入ります |
| アクション | actions スロットに入れる Tanrei のボタン。確定のアクションには confirming、取り消しのアクションには dismissive を付けます。ボタングループと同じ並べ方で配置し、ラベルが収まらなくなると縦に積みます |
| 矢印 | アンカーを指す三角形。任意です。arrow は 1 つのポップオーバーに、comp.popover.arrow.visible はデザインシステム全体に矢印を付けます |
variant のないアクションは、comp.popover.actions.variant(既定のシステムでは Tonal)になります。閉じるボタンは、アイコンだけの小さいボタンで、バリアントは comp.popover.close-button.variant です。既定のシステムでは、Tanrei のほかの閉じるボタンと同じく Tonal で、どんなコンテンツの上でもコントロールとして読めます。m3-like と cupertino のプリセットは、グリフだけの見た目(Text)のままです。
ポップオーバーは、Tanrei で現れるほかのすべての面と同じように現れます。アンカーに面した辺の中央から、フェードインしながら少し(デフォルトのシステムでは 96 % から)拡大し、閉じるときは同じ道を少し速く戻ります。モーションの途中で開閉が切り替わると、その時点の状態から折り返します。動きを減らす設定では、フェードだけになります。m3-like プリセットでは、Material のメニューと同じく、その辺から展開します。ページのマークアップに open で書かれたポップオーバーは、最初のフレームからモーションなしで表示されます。
comp.popover.compact.window-width(既定のシステムでは 600)より狭いウィンドウでは、ポップオーバーはボトムシートの見た目になります。これは、コンパクトなウィンドウでのすべてのポップオーバーとダイアログに対する Tanrei の規則です。開くのは同じポップオーバーで、props とイベントも同じです。変わるのは描き方だけです。
- ウィンドウの下端に付き、
comp.bottom-sheet.container.max-widthまでウィンドウの幅いっぱいに広がり、ボトムシートの色とエレベーションで描かれます。上の角はcomp.bottom-sheet.container.shape、下の角は直角です。画面キーボードが出ているときは、その上に載ります。 - 高さは中身に合わせますが、上に
comp.bottom-sheet.container.margin-block-startを残した高さを超えません。超えた分は、ヘッダーとアクションの間でコンテンツがスクロールします。 - 矢印もドラッグハンドルもなく、ドラッグにも追従しません。
placement、offset、crossOffset、widthは使われません。 - モーダルです。ボトムシートのスクリムがページを暗くし、後ろのものはすべて操作不能になり、フォーカスは中にとどまります。Escape、戻る、スクリムを押すこと、閉じるボタンで閉じます。
- ボトムシートのモーションで下端からせり上がり、閉じるときは下へ戻ります。動きを減らす設定では、その場でフェードします。
- 開いている間にウィンドウの幅がこの値をまたぐと、モーションなしでその場で形を変え、フォーカスはそのままです。
compactPresentation を own(Web では compact-presentation="own")にすると、そのポップオーバーはどのウィンドウでもアンカーの横にとどまります。属するものの隣にいなければならないサーフェスに使います。initialFocus="anchor" のポップオーバーは、入力中のフィールドを使える状態に保つため、常にアンカーの横にとどまります。シートをまったく使わないデザインシステムは、comp.popover.compact.window-width を 0 にします。
ここでいうポップオーバーにメニューは含まれません。メニュー、セレクトやコンボボックスのリスト、ボタンのメニューは、どのウィンドウでもコントロールの横にとどまります。
構成要素
| パーツ | 説明 |
|---|---|
1 anchor | The element the popover stands beside: the product's own control (a Button, an icon-only Button, a chip, a field). The popover reads its place, never draws or restyles it. It is also the opener — the element a person presses to open the popover, which carries the open state and takes focus back — unless the product names another element as the opener (a field that anchors the surface, with a button inside it that opens it). |
2 scrim · 任意 | In a compact window only, while the popover is presented as a sheet: the bottom sheet's scrim over everything behind it (POP-40). It blocks presses and asks the popover to close when pressed. |
3 container | The raised surface that holds everything else, placed on one side of the anchor and aligned along it; in a compact window, with the bottom sheet's appearance at the window's bottom edge (POP-39). It answers no pointer and no press itself and has no states: what is operated is inside it. |
4 arrow · 任意 | A triangle on the container's edge that faces the anchor, pointing at it; one shape with the container. |
5 header · 任意 | The row at the top that holds the headline and the close button. It stays in place while the content scrolls. |
6 headline · 任意 | What the popover is about ("Filters", "Share this page"). It is the popover's accessible name where it is given. |
7 close-button · 任意 | An icon-only Button of the small size showing the close glyph at the end of the header, in the variant comp.popover.close-button.variant names (tonal in the default system, as every host's close button is). The popover places it and chooses its variant; everything else it reads from Button's own tokens. |
8 content | Whatever the product puts in the popover: text, links, fields, a list, a few settings. The popover insets it, hands it a content color and scrolls it between the header and the actions; it does not lay it out. |
9 divider · 任意 | Hairlines across the container below the header and above the actions, drawn only while the content scrolls: for as long as it does in the default system, or only while content lies beyond each line under dividerVisibility scroll. |
10 actions · 任意 | Tanrei Buttons at the foot of the popover, each marked confirming or dismissive where it is one, laid out as a Button group. A Button whose variant the product did not set is tonal in the default system. Optional: a popover whose content acts at once (a setting, a link) needs none. |
プロパティ
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
open | boolean | false | Whether the popover is shown. The product opens it when its opener is pressed, and never on hover or on focus. Where open is a plain value (web, Compose, React Native) the product closes the popover by setting it to false when the popover emits close; where it is a binding or a controller (SwiftUI, Flutter) the popover closes itself through it (POP-21). |
placement | top | top-start | top-end | bottom | bottom-start | bottom-end | start | start-top | start-bottom | end | end-top | end-bottom | auto | "bottom-start" | Where the popover prefers to stand: a side of the anchor, and how it lines up along that side (POP-2). The thirteen values are the tooltip's, read the same way; the menu's four are among them. top and bottom centre it above or below the anchor; with -start or -end its start or end edge meets the anchor's. start and end centre it beside the anchor; with -top or -bottom its top or bottom edge meets the anchor's. auto takes the first side with room (POP-3). Start and end follow the layout direction. The popover flips to the opposite side when the preferred one has no room, keeping the alignment (POP-5). The default is the menu's: below the anchor, start edges aligned. |
offset | number | null | The distance between the anchor's edge and the popover, in the platform's layout units; 0 lets them touch. Null takes comp.popover.offset, the system's answer for every popover. Set it only where one anchor's box is not what the eye takes for its edge. |
crossOffset | number | 0 | How far the popover moves along the side it stands on, in the platform's layout units, from where placement puts it: positive towards the end for a popover above or below, towards the bottom for one beside. It never moves the popover off the anchor's side (POP-4). |
arrow | boolean | null | Whether the popover draws an arrow that points at the anchor. Null, the default, takes comp.popover.arrow.visible, the system's choice, which is off in the default system (POP-10). |
width | content | anchor | "content" | What decides the container's width. content: as wide as its content, between comp.popover.container.min-width and comp.popover.container.max-width. anchor: as wide as the anchor, for a surface that drops from a field and should read as its continuation (POP-7). A structural choice drawn by code, not a token axis. |
compactPresentation | sheet | own | "sheet" | How the popover is presented in a compact window, one narrower than comp.popover.compact.window-width. sheet: with the bottom sheet's appearance at the window's bottom edge, as a modal dialog (POP-39 to POP-41), as every popover and every dialog of Tanrei is there. own: as a popover in every window, beside its anchor and not modal, narrowing to the window (POP-43), for a surface that must stay beside what it belongs to (a note beside the word it explains). initialFocus anchor keeps the popover's own presentation whatever this says. A structural choice drawn by code, not a token axis. |
inset | boolean | true | Insets the content by the container's paddings. Set it to false for content that pads itself or reaches the edges (a list whose rows carry their own padding, an image); the header and the actions keep their insets. |
initialFocus | popover | anchor | "popover" | Where focus is when the popover has opened. popover: focus moves into it (POP-19), which is right whenever the opener is a button. anchor: focus stays where it is, for a popover that opens under a field the person is typing in, whose keys the field keeps; Tab then moves into the popover (POP-20). It also keeps the popover beside its anchor in a compact window (POP-43): a modal sheet would make the field inert. |
headline | string | "" | What the popover is about, drawn in its header and used as its accessible name. Empty draws no headline; the popover is then named by label. |
label | string | "" | Names the popover for assistive technology where it draws no headline ("Text formatting"). Never drawn. Required while headline is empty; ignored while headline is given. |
closable | boolean | false | Draws the close button at the end of the header. Escape and a press outside already close a popover, so the button is for a popover large enough to cover what a person would press outside it, and for touch, where there is no Escape. |
closeLabel | string | "" | The close button's accessible name ("Close filters"). Required while closable is true. |
dividerVisibility | auto | always | scroll | "auto" | When the dividers below the header and above the actions are drawn while the content scrolls between them. always: both, for as long as the content scrolls, wherever it is scrolled to. scroll: each only while content lies beyond it — below the header while the content is not at its top, above the actions while more of it lies below. auto takes comp.popover.divider.visibility, the design system's answer (always in the default system). Under every choice a popover whose parts all fit draws none. |
スロット
| スロット | 説明 |
|---|---|
default | The content, below the header. A fixture's content string is plain text, which takes sys.typescale.body in the content color (POP-14). |
actions | The action items: Tanrei Buttons, each marked confirming or dismissive where it is one. The popover places them (POP-15) and gives those without a variant its own (POP-16). A fixture fills it through its items. |
イベント
| イベント | 説明 |
|---|---|
close | The popover asks to be closed, every overlay opened from inside it with it: Escape or the platform's back, a press outside, focus leaving it, its close button, or its anchor going out of sight. Carries which of these it was, named escape, back, outside, focus-out, close or anchor-hidden (in the platform's casing). An action never causes it (POP-17). A popover presented as a sheet sends escape, back, outside (a press on its scrim) or close, never focus-out or anchor-hidden (POP-40). |
アイテム
このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。
action
One action at the foot of the popover: a Tanrei Button, placed by the popover as a Button group places its items and given the popover's variant unless the product set one.
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
variant | string | "" | The Button's variant (filled, tonal, elevated, outlined, text, or one a system added). Empty, the default: the popover gives it the variant comp.popover.actions.variant names, tonal in the default system (POP-16). Set it to give one action another look. |
disabled | boolean | false | As the Button's disabled. A disabled action keeps its place and its share of the width. |
confirming | boolean | false | The action completes what the popover is for (Apply, Save, Share). Placed at the end of a row and at the top of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19). |
dismissive | boolean | false | The action backs out (Cancel, Reset). Placed at the start of a row and at the bottom of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19). |
| スロット | 説明 |
|---|---|
label | The Button's label, as its default slot. A fixture item's content is this label. |
| イベント | 説明 |
|---|---|
click | The action was activated, as the Button's click. The popover does not close by itself (POP-17). |
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| アイテム | tanrei-button | TanreiButton | TanreiButton | TanreiButton | Button |
variant · プロパティ | variant | variant | variant | variant | variant |
disabled · プロパティ | disabled | disabled | enabled | onPressed | disabled |
confirming · プロパティ | confirming | tanreiGroupPlacement(.confirming) | Modifier.confirming() | TanreiButtonGroupItem.placement | confirming |
dismissive · プロパティ | dismissive | tanreiGroupPlacement(.dismissive) | Modifier.dismissive() | TanreiButtonGroupItem.placement | dismissive |
click · イベント | click | action | onClick | onPressed | onPress |
| 備考 | A tanrei-button child with slot="actions": its label is its content. confirming and dismissive are boolean attributes that the popover reads, as the button group does. A Button without a variant attribute takes the popover's variant through the internal controller the group already gives its items (POP-16); the attribute, once written, wins. | A TanreiButton in the actions: builder. The flags are the button group's .tanreiGroupPlacement(_:) modifier (one enum: both cannot be set). A Button created with variant: nil reads the popover's variant from an environment value the popover sets (internal to the package). | A TanreiButton in the actions lambda, whose receiver is the button group's scope, so the flags are its Modifier.confirming() and Modifier.dismissive(). enabled is disabled's inverse. variant: TanreiButtonVariant? = null takes the popover's from a CompositionLocal (internal to the library). | A TanreiButton in actions:; a null onPressed disables it. The flags are the button group's TanreiButtonGroupItem(placement:, child:) (one enum: both cannot be set). A Button with variant: null takes the popover's from an InheritedWidget (internal to the package). | A Button in the actions prop. confirming and dismissive are props the popover reads from its actions, as the button group reads them. A Button with no variant takes the popover's from a React context (internal to the package). |
状態
振る舞い
EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。
| ID | 要件 |
|---|---|
POP-1 | WHEN open becomes true THE popover SHALL present its container in the platform's overlay layer, the one the menu's surface uses (the web's top layer through the popover API, an overlay SwiftUI draws above its anchor, Compose's Popup, Flutter's Overlay, React Native's overlay layer that TanreiProvider renders), above the other content of its window at comp.popover.container.elevation; WHILE it is open beside its anchor — in every window but a compact one where it is a sheet (POP-39, POP-40) — THE popover SHALL NOT trap focus, SHALL NOT make any other part of the window inert, unscrollable or hidden from assistive technology and SHALL draw no scrim: it is an operated overlay that is not modal. WHEN open becomes false THE popover SHALL remove the container. |
POP-2 | THE popover SHALL read placement as a side and an alignment along that side, as the tooltip reads the same thirteen values (TIP-22): top and bottom SHALL put the container above or below the anchor with its centre on the anchor's, top-start and bottom-start with its start edge on the anchor's start edge, and top-end and bottom-end with its end edge on the anchor's end edge; start and end SHALL put it beside the anchor, on the side the layout direction gives that name, with its centre on the anchor's, start-top and end-top with its top edge on the anchor's top edge, and start-bottom and end-bottom with its bottom edge on the anchor's bottom edge. WHEN placement changes while the popover is shown THE popover SHALL take its new place at once, without a motion, keeping its focus. |
POP-3 | WHILE placement is auto THE popover SHALL try the sides in the order bottom, top, end, start and SHALL take the first that has room for the container within comp.popover.window-margin, or the one with the most room WHEN none has, with its start edge on the anchor's start edge for bottom and top and its top edge on the anchor's top edge for end and start, and SHALL then shift, hold and follow as POP-5 says, keeping the side it took for as long as that side has room. |
POP-4 | THE popover SHALL take the distance between the anchor's edge and the nearest part of itself — the container's edge, or the arrow's tip while an arrow is drawn — from offset, a number of the platform's layout units used as given, so that 0 lets the two touch, and from comp.popover.offset WHILE offset is null; it SHALL then move the container along its side by crossOffset, before the shift of POP-5, and by no more than leaves comp.popover.arrow.width of the container beside the anchor, so that the popover still stands on the anchor's side and an arrow can still reach the anchor. IF offset is negative or not a finite number THE popover SHALL use comp.popover.offset in its place, IF crossOffset is not a finite number THE popover SHALL use 0, and in debug builds it SHALL report the value it ignored. |
POP-5 | THE popover SHALL keep its container at least comp.popover.window-margin inside the window's safe area by these steps, in this order: WHEN the preferred side has no room for the container and the opposite side has, it SHALL flip to the opposite side, keeping the alignment; WHEN neither has room it SHALL use the one of the two with more room and limit the container to that room (POP-7 for its width, POP-8 for its height); it SHALL then shift along the side as far as the margin asks and no farther; and WHERE the container is still larger than the room on its side (a window smaller than the popover's least size) it SHALL move the container back into the margin over the anchor, its top and start edges first, rather than leave the window. It SHALL follow the anchor when the anchor moves (scrolling, resizing, layout), taking these steps again from the side it is on, which it SHALL keep for as long as that side has room, so that a page that scrolls does not throw the popover from side to side. WHEN the anchor is removed, hidden, scrolled out of the window, or scrolled out of sight inside a scrolling ancestor so that no part of it can be seen THE popover SHALL emit close once with the reason anchor-hidden and be closed as POP-21 says, because a surface with nothing beside it no longer says what it belongs to. |
POP-6 | THE popover SHALL fill its container with comp.popover.container.color, raise it by comp.popover.container.elevation, round it by comp.popover.container.shape eased by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0), and clip its header, content and actions to that shape. THE popover SHALL NOT change anything the container draws under a pointer, a press or focus: the container has no hovered, focused or pressed state and draws no state layer. |
POP-7 | WHILE width is content THE popover SHALL make its container as wide as its widest part (its header, its content, or its actions in one row at their own widths) and its paddings, at least comp.popover.container.min-width and at most comp.popover.container.max-width, wrapping content that would be wider; WHILE width is anchor it SHALL make the container as wide as the anchor, and at least comp.popover.container.min-width where the anchor is narrower, comp.popover.container.max-width not applying, and SHALL follow the anchor's width as it changes. In either case the container SHALL never be wider than the window's safe area less comp.popover.window-margin on each side, narrowing below comp.popover.container.min-width where the window asks it to. |
POP-8 | THE popover SHALL make its container as tall as its parts and its paddings, at most comp.popover.container.max-height and at most the room its side leaves within comp.popover.window-margin (POP-5). WHEN its parts do not fit in that height THE popover SHALL keep the header and the actions in place and scroll the content between them, SHALL let keyboard focus and assistive technology scroll it, and SHALL draw a divider of comp.popover.divider.width in comp.popover.divider.color across the container below the header and another above the actions: WHILE its divider visibility (POP-38) is always, both for as long as the content scrolls, wherever it is scrolled to, at its top included; WHILE it is scroll, the one below the header only WHILE the content is scrolled away from its top and the one above the actions only WHILE more of it lies below; WHILE everything fits, or there is no header or no action on that side of the content, THE popover SHALL draw no divider there. WHEN the header and the actions alone leave no room for the content (large text in a short window) THE popover SHALL scroll all of its parts together instead, so that nothing is cut off or unreachable. The dividers SHALL be hidden from assistive technology and SHALL show and hide at once, without motion. |
POP-9 | THE popover SHALL lay out, top to bottom, inside comp.popover.container.padding-block at the top and bottom edges of its container: WHILE headline is not empty or closable is true, the header (POP-11); then the content, comp.popover.container.gap below the header where there is one; then, WHILE the popover has at least one action, the actions, comp.popover.actions.margin-block-start below the content, or below the header where there is no content. WHILE inset is true THE popover SHALL inset the content by comp.popover.container.padding-inline from the container's start and end edges; WHILE inset is false it SHALL let the content reach the container's start and end edges — and its top and bottom edges where no header or no action stands there, the block padding then not applying on that side — clipped by the container's shape, the header and the actions keeping their own insets. |
POP-10 | THE popover SHALL draw an arrow WHILE arrow is true, and WHILE arrow is null and comp.popover.arrow.visible is on, and none otherwise. WHILE it draws one THE popover SHALL draw a triangle comp.popover.arrow.width wide at its base and comp.popover.arrow.height tall on the container's edge that faces the anchor, as one shape with the container — the container's color and elevation, with no seam and no shadow between the two — its tip pointing at the middle of the part of the anchor's edge that the container stands beside, and SHALL keep its base at least comp.popover.arrow.inset, and at least the corner's radius, from each end of that edge, standing at the edge's middle where the edge is too short for both; the container SHALL stand comp.popover.arrow.height farther from the anchor, so that the tip keeps the offset (POP-4). WHEN the container flips, shifts or follows the anchor (POP-5) the arrow SHALL move with it to the edge that now faces the anchor and point at the anchor again, and WHILE the container covers the anchor (POP-5) THE popover SHALL draw no arrow. The arrow SHALL take no press of its own and SHALL be hidden from assistive technology. |
POP-11 | WHILE headline is not empty or closable is true THE popover SHALL draw the header as one row: the headline from comp.popover.container.padding-inline after the container's start edge, taking the width the close button leaves, and, WHILE closable is true, the close button (POP-13) at the row's end, comp.popover.header.gap after the headline and with its edge comp.popover.container.padding-inline before the container's end edge, both aligned to the row's first line; the header SHALL be as tall as the taller of the two and SHALL stay in place while the content scrolls (POP-8). |
POP-12 | WHILE headline is not empty THE popover SHALL draw it in comp.popover.headline.typescale and comp.popover.headline.color, wrapping onto more lines rather than truncating and growing the header with it, and SHALL expose it as a heading. THE popover SHALL set the headline's letter-spacing by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them. |
POP-13 | WHILE closable is true THE popover SHALL draw at the end of its header an icon-only Button of the small size showing the glyph close of the system's icon set and named by closeLabel, in the variant comp.popover.close-button.variant names, given through the channel a Button reads from the component that places it, and otherwise unrestyled: it reads Button's own tokens, answers hover, focus and press as any Button does and keeps its own minimum target (BTN-5); WHEN the user activates it THE popover SHALL emit close exactly once with the reason close. WHILE closable is true and closeLabel is empty it SHALL still draw the button, and in debug builds report that the button has no accessible name. THE popover SHALL draw no close mark of its own (no path, shape or character of its own). |
POP-14 | THE popover SHALL hand its content comp.popover.content.color as the platform's inherited foreground (the web's inherited color, SwiftUI's foreground style, Compose's content color, Flutter's default text and icon style, React Native's Tanrei text context); plain text given as content, where the platform must style it, SHALL take sys.typescale.body. THE popover SHALL NOT lay out, restyle or mirror the content itself, and SHALL let it hold any element that takes input — links, Buttons, fields, a list, a menu's or another popover's opener. |
POP-15 | WHILE the popover has at least one action THE popover SHALL lay out its actions as a standard Button group lays out its items (BGR-12 to BGR-19), comp.popover.actions.gap apart and comp.popover.container.padding-inline from the container's start and end edges, in the order the group gives them — dismissive at the start and confirming at the end of a row, confirming on top and dismissive at the bottom of a column, unflagged actions between them in the order given — with focus and reading order following that order (BGR-15). WHILE comp.popover.actions.arrangement is fill it SHALL make them a full-width group across the container's inner width, the actions sharing a row equally (BGR-17) or each as wide as the column; WHILE it is end it SHALL keep them at their own widths, the row aligned to the end, and a column as wide as its widest action and aligned to the end. It SHALL place them in a row WHILE every action's label fits its share on one line (fill) or the row at its own widths fits the inner width (end), and otherwise in a column, re-deciding when the container's width or the text size changes. |
POP-16 | WHILE an action's variant is empty THE popover SHALL give that Button the variant comp.popover.actions.variant names, through the channel a Button reads from the component that places it, so that the Button renders from that variant's tokens and states as it would with the variant set; WHILE an action's variant is set THE popover SHALL leave it as set. THE popover SHALL NOT change an action's size or tokens, and SHALL NOT change its look or emphasis for its confirming or dismissive (BGR-19). |
POP-17 | WHEN the user activates an action THE popover SHALL let the Button emit its own click and SHALL NOT close, emit close or otherwise answer it itself, whether the action is confirming, dismissive or neither; the product closes the popover from the action's handler by setting open to false, and focus then returns as POP-22 says. |
POP-18 | THE popover SHALL be shown exactly while open is true: it SHALL NOT open or close itself when a pointer enters or leaves its anchor or its container, when focus arrives at its anchor, on a long-press or after a time, and SHALL have no show or hide delay; the product sets open when the opener is activated by pointer, touch, Enter or Space, and sets it to false when the opener is activated again. THE popover SHALL expose on its opener that it opens a dialog, whether that dialog is open and which element it is, where the platform has those properties (aria-haspopup with the value dialog, aria-expanded and aria-controls on the web), and SHALL NOT change the opener's accessible name, role or look. The opener is the anchor unless the product names another element as the opener, in which case the container is placed against the anchor (POP-2 to POP-5) and everything this spec says of the opener holds for that element. |
POP-19 | WHEN open becomes true WHILE initialFocus is popover THE popover SHALL move focus to the element in it that the product marked for initial focus (the platform's own mark, named in the platform notes) or, without one, to its container, so that assistive technology reads its name first and the next Tab reaches its first focusable element; it SHALL show a focus indicator on the container only when the popover was opened from the keyboard, as sys.behavior.focus-style names. WHEN open becomes true WHILE initialFocus is anchor THE popover SHALL leave focus where it is and SHALL take no key from the element that has it except Escape (POP-23). Focus SHALL move when the popover opens, not when its motion ends; a popover mounted with open true SHALL NOT move focus. |
POP-20 | WHILE the popover is open THE popover SHALL stand in the focus order and in the reading order of assistive technology directly after its opener, wherever the platform draws it: Tab from the opener SHALL move to the popover's first focusable element, Shift+Tab from that element SHALL move back to the opener, Tab and Shift+Tab inside the popover SHALL move through its close button, its content's focusable elements and its actions in the order they are placed, and Tab from its last focusable element SHALL move to the element that follows the opener in the page's order, which closes the popover as focus leaving it does (POP-21). Focus on the opener SHALL count as focus that has not left the popover, so that Shift+Tab to the opener leaves the popover open. THE popover SHALL NOT wrap focus from its last element to its first. |
POP-21 | WHEN the user presses Escape or the platform's back (Android's back gesture or button, the iOS accessibility escape gesture), presses outside the container, its arrow, its anchor, its opener and every overlay opened from inside it (POP-24), moves focus to an element outside all of those, or activates the close button (POP-13), or WHEN the anchor goes out of sight (POP-5) THE popover SHALL emit close once with that reason (escape, back, outside, focus-out, close or anchor-hidden) and SHALL be closed: through its binding or controller where open is one (SwiftUI, Flutter), and by the product setting open to false where open is a plain value (web, Compose, React Native). A press on the opener or the anchor is never a press outside: the product's own handler closes the popover from it (POP-18), so that one press never closes and opens it again. Focus leaving the window (another window or application taking it) SHALL NOT count as focus leaving the popover, and neither SHALL a move of an assistive technology's reading cursor that moves no input focus. Whether a press outside also reaches the element under it SHALL follow the platform and be stated in its README, as for the menu (MNU-5). |
POP-22 | WHEN the popover closes THE popover SHALL return focus to its opener IF focus was inside the popover or an overlay opened from inside it, or was lost with it (a press outside that landed on nothing focusable), and SHALL leave focus where the person put it IF the close came from focus moving to, or a press landing on, another focusable element; WHERE the opener no longer exists or cannot take focus it SHALL fall to the platform's default for a closed overlay. It SHALL show the opener's focus indicator only when the close came from the keyboard. Focus SHALL move when the popover closes, not when its motion ends. |
POP-23 | WHILE the popover is open, WHEN the user presses Escape or the platform's back and neither an overlay opened from inside it nor an element inside it has taken the key for itself (an open menu, a tooltip, a field composing text or clearing its value) THE popover SHALL take the key and close as POP-21 says, and SHALL let it go no further, so that it does not also close a dialog or a sheet the popover stands in; WHILE several overlays are open Escape SHALL close only the topmost. |
POP-24 | WHEN an overlay opens from an element inside the popover's container — a menu, a tooltip, a docked picker or a select's surface, another popover, a dialog or a modal sheet — THE popover SHALL treat it as its own: a press inside it SHALL NOT count as a press outside, focus moving into it SHALL NOT count as focus leaving, and the popover SHALL stay open under it, inert with the rest of the window WHILE that overlay is modal. WHEN that overlay closes THE popover SHALL receive back the focus it returns. WHEN the popover is closed, for a reason POP-21 names or because the product sets open to false, THE popover SHALL close every overlay opened from inside it that is not modal with it at once, their own close events carrying no reason of the person's, and SHALL return focus to its own opener as POP-22 says, not to an opener inside it. WHEN a popover is opened from inside a popover that was itself opened from inside a popover THE popover SHALL still work, and in debug builds SHALL report the depth. |
POP-25 | WHERE the popover's anchor is inside a modal overlay (a dialog, a modal sheet) THE popover SHALL be presented above that overlay and belong to it: operable while the rest of the window is inert, and in that overlay's focus loop directly after its opener (POP-20). WHEN a popover opens while another popover that it was not opened from is open THE popover SHALL be drawn above it; THE popover SHALL NOT close another popover by opening — the press or the focus that opened it closes the other as POP-21 says — so that the product that sets two open at once has two. |
POP-26 | WHEN the popover opens beside its anchor THE popover SHALL make its container appear as every anchored surface appears: from sys.motion.appear.opacity, sys.motion.appear.scale, sys.motion.appear.distance toward the anchor and sys.motion.appear.blur to whole, grown from the middle of the edge that faces the anchor — from its own centre WHERE sys.motion.appear.origin is center, and WHILE it covers its anchor (POP-5) — over comp.popover.motion.open-duration with comp.popover.motion.open-easing, and WHEN it closes THE popover SHALL take it back the same way to where it started, over comp.popover.motion.close-duration with comp.popover.motion.close-easing. WHERE sys.behavior.appearance is unfold the scale SHALL apply along the axis that leaves the anchor only, so that the container unfolds from that edge at its full width. WHERE motion is reduced THE popover SHALL only fade it in and out, over sys.motion.role.reduced, with no scale, distance or blur. WHEN it is mounted with open true THE popover SHALL appear without its motion while the document has not yet been first painted, and with it when it is mounted after that. The motion SHALL be drawn only: the place POP-5 gives and the room the popover is measured for are those of the popover at rest. |
POP-27 | WHEN the popover is told to close while it is still opening, or to open while it is still closing THE popover SHALL turn round from the opacity and the size it has reached, without first jumping to the start or the end of either motion, and SHALL take no longer over the rest than the whole duration of the motion it turns to. WHILE the popover is closed or closing THE popover SHALL take no pointer input and no focus — a press reaches what lies under it — and WHILE it is closed it SHALL take no room in the layout, draw nothing and expose nothing to assistive technology. |
POP-28 | THE popover SHALL expose its container as a dialog named by headline or, WHILE headline is empty, by label: one that is not modal, hiding nothing outside it from assistive technology, WHILE it stands beside its anchor, and a modal dialog WHILE it is presented as a sheet (POP-40); the close button SHALL be named by closeLabel, each action by its own label, and the arrow and the dividers SHALL be hidden. THE popover SHALL NOT expose its text as its anchor's description: what describes an anchor is a tooltip. |
POP-29 | IF headline and label are both empty THE popover SHALL still open, and in debug builds SHALL report that it has no accessible name. IF the popover opens with no content, no headline and no action THE popover SHALL draw nothing, take no focus and expose nothing, and in debug builds SHALL report an empty popover. IF no anchor can be found when open becomes true THE popover SHALL stay closed, emit nothing, and in debug builds report the missing anchor. |
POP-30 | WHILE the text size is enlarged THE popover SHALL keep its width limits (POP-7), wrap its headline and let its content wrap, grow its container's height with them up to the limits of POP-8 and scroll beyond them, stack its actions once a label no longer fits its share (POP-15), and SHALL NOT scale the close button, the arrow, the offset or the window margin beyond what the platform's layout units do. Nothing in the header or the actions SHALL be clipped or truncated. |
POP-32 | WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE popover SHALL draw an edge sys.border.hairline wide in the platform's text color round its container and its arrow, as one outline, because the mode removes the shadow and may give the container the page's color; its headline and plain text SHALL take the platform's text color. |
POP-33 | THE popover SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the popover is shown THE popover SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus, the container placed again by the new direction (POP-34). |
POP-34 | WHEN the layout direction is right-to-left THE popover SHALL take the side start as the anchor's right side and the side end as its left, SHALL take the alignments -start and -end of a popover above or below as the right and the left edges, and a positive crossOffset of such a popover as a move towards the left, flipping and shifting from there as POP-5 says; it SHALL leave the sides top and bottom, the alignments -top and -bottom, a crossOffset beside the anchor and the centring on the anchor as they are, and SHALL keep auto's order of sides by their names (POP-3). It SHALL mirror its header — the headline at the right, the close button at the left — apply its paddings by logical start and end, and place its actions as a Button group places them right to left (BGR-8); it SHALL NOT mirror the glyph close, the arrow's shape, or its content, which follows the layout direction itself. |
POP-35 | THE popover SHALL lay out its headline as text of its own direction, isolated from the text around it: it takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE popover SHALL still place the headline and align its lines by the layout direction, not by the text's own. |
POP-36 | WHILE the on-screen keyboard is shown THE popover SHALL take the keyboard's top edge as an edge of the window's safe area (POP-5), and SHALL keep to the visible part of the window wherever that is smaller than the window: it SHALL flip, shift or shorten so that no part of it is under the keyboard, and SHALL scroll the focused element into the room left; WHEN the keyboard hides it SHALL take its place again by POP-5. It SHALL move as the platform moves content for its keyboard, and at once where motion is reduced. |
POP-37 | WHEN placement, width or initialFocus names a value the popover does not define THE popover SHALL render with the default value, and in debug builds report the unknown value. |
POP-38 | THE popover SHALL draw its dividers (POP-8) as its dividerVisibility names: WHILE dividerVisibility is auto, as comp.popover.divider.visibility names, which follows sys.behavior.divider-visibility unless a design system sets the popover apart; WHILE dividerVisibility is always or scroll, as that value names, whatever the tokens say. WHEN dividerVisibility, or the token it follows, changes THE popover SHALL draw the other form at once, moving no part and keeping the scroll position and focus; WHEN dividerVisibility names a value the popover does not define THE popover SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, always, and state that in its README. |
POP-39 | WHILE the window is narrower than comp.popover.compact.window-width (a compact window), compactPresentation is sheet and initialFocus is popover THE popover SHALL present its container with the bottom sheet's appearance (BSH-7) instead of beside its anchor: at the window's bottom edge, as wide as the window but no wider than comp.bottom-sheet.container.max-width and centred in it, filled with comp.bottom-sheet.container.color, raised by comp.bottom-sheet.container.elevation, its top corners rounded by comp.bottom-sheet.container.shape eased by sys.shape.corner-smoothing and its bottom corners square, its color reaching under the bottom system inset and its parts above it, and sitting on the on-screen keyboard while it is shown; as tall as its parts, but never taller than the window below its top system inset less comp.bottom-sheet.container.margin-block-start, its content scrolling beyond that as POP-8 says. It SHALL draw no arrow and no drag handle, follow no drag, close on no swipe, and ignore placement, offset, crossOffset and width there; its header, content and actions SHALL keep their own tokens and layout (POP-9 to POP-17, POP-38) across the sheet's width. WHERE comp.popover.compact.window-width is 0 no window is compact. |
POP-40 | WHILE the popover is presented as a sheet (POP-39) THE popover SHALL be a modal overlay, as a modal bottom sheet is (BSH-2): it SHALL draw comp.bottom-sheet.scrim.color with comp.bottom-sheet.scrim.blur over the whole window behind it (MDL-1), make everything outside it and the overlays opened from inside it inert, keep the content behind it from scrolling, keep focus inside it — Tab and Shift+Tab wrap between its last and first focusable elements — and expose its container as a modal dialog (POP-28). WHEN it opens it SHALL move focus into it as POP-19 says for initialFocus popover — mounted with open true as well, because everything behind it is then inert (as a modal sheet does, BSH-3) — and WHEN it closes it SHALL return focus to its opener as POP-22 says. A press on the scrim SHALL ask to close with the reason outside, Escape with escape (POP-23) and the platform's back with back, and the close button with close (POP-21); since focus cannot leave it and it does not stand beside its anchor, it SHALL NOT ask with focus-out or anchor-hidden and SHALL NOT follow its anchor. Its opener SHALL still say that it opens a dialog and whether it is open (POP-18). |
POP-41 | WHEN the popover opens as a sheet (POP-39) THE popover SHALL slide its container up from below the window's bottom edge into its place while fading the scrim in, as a modal bottom sheet opens (BSH-1), over comp.bottom-sheet.motion.enter-duration with comp.bottom-sheet.motion.enter-easing, and WHEN it closes there it SHALL slide the container down out of view while fading the scrim out, over comp.bottom-sheet.motion.exit-duration with comp.bottom-sheet.motion.exit-easing, turning round from where it has got to as POP-27 says. WHERE motion is reduced it SHALL fade in and out in place without sliding, or appear and disappear at once on a platform whose reduced-motion setting removes every animation (Android's). Focus SHALL move when it opens or closes, not when its motion ends. |
POP-42 | WHEN the window's width crosses comp.popover.compact.window-width, or compactPresentation or initialFocus changes, while the popover is open THE popover SHALL take the other presentation at once, without a motion — beside its anchor and not modal (POP-1 to POP-5), or as a sheet (POP-39, POP-40) — keeping its open state, its scroll position and the element in it that has focus, and SHALL emit nothing; IF focus is outside it (on its opener) as it becomes a sheet THE popover SHALL move focus into it as POP-19 says, because the opener is then inert. |
POP-43 | WHILE compactPresentation is own, or initialFocus is anchor, THE popover SHALL stay an anchored popover that is not modal in every window, compact ones included: WHERE the window is narrower or shorter than the container wants THE popover SHALL narrow and shorten the container as POP-7 and POP-8 say, cover its anchor as POP-5 says where it must, and scroll, and SHALL NOT draw a scrim or trap focus. A popover whose initialFocus is anchor stays beside its anchor because focus stays in the element the person is typing in, which a modal sheet would make inert. WHEN compactPresentation names a value the popover does not define THE popover SHALL treat it as sheet and in debug builds report the unknown value. |
POP-44 | WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE popover SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on its dialog (POP-28), beside its anchor and as a sheet alike — an id reference by the elements it names in the host's tree or a tree around it, a description after its own. WHILE headline or label is not empty it is the dialog's name (POP-28), over a name given so; WHILE both are empty, a name given so is the dialog's name and SHALL NOT be reported as having none in debug builds. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE popover SHALL take it off that element. |
プラットフォーム別の名前対応
パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。
| 仕様 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| コンポーネント | tanrei-popover | tanreiPopover | TanreiPopover | TanreiPopover | Popover |
open · プロパティ | open | isPresented | visible | controller | visible |
placement · プロパティ | placement | placement | placement | placement | placement |
offset · プロパティ | offset | offset | offset | offset | offset |
crossOffset · プロパティ | crossOffset | crossOffset | crossOffset | crossOffset | crossOffset |
arrow · プロパティ | arrow | arrow | arrow | arrow | arrow |
width · プロパティ | width | width | width | width | width |
compactPresentation · プロパティ | compactPresentation | compactPresentation | compactPresentation | compactPresentation | compactPresentation |
inset · プロパティ | inset | inset | inset | inset | inset |
initialFocus · プロパティ | initialFocus | initialFocus | initialFocus | initialFocus | initialFocus |
headline · プロパティ | headline | headline | headline | headline | headline |
label · プロパティ | label | accessibilityLabel | contentDescription | semanticLabel | accessibilityLabel |
closable · プロパティ | closable | closable | closable | showCloseButton | closable |
closeLabel · プロパティ | closeLabel | closeLabel | closeLabel | closeButtonSemanticLabel | closeLabel |
dividerVisibility · プロパティ | dividerVisibility | dividerVisibility | dividerVisibility | dividerVisibility | dividerVisibility |
close · イベント | close | onClose | onDismissRequest | onClose | onClose |
| 備考 | <tanrei-popover anchor="…"> names its anchor as the menu does: anchor is the id of the anchor element in the popover's own tree, and anchorElement a property for an element that an id cannot reach (another shadow root); opener and openerElement name a separate opener, as the docked picker's do (POP-18). The popover sets aria-haspopup="dialog", aria-controls and aria-expanded on the opener and removes them when it is disconnected. Attributes are kebab-case (cross-offset, initial-focus, close-label, compact-presentation); offset and crossOffset are number properties in CSS pixels parsed as the tooltip's are, so an empty attribute is not a number; arrow is true, false or null — absent for null, present for true and ="false" for false; inset is a boolean property that defaults to true, whose attribute is the inverse no-inset. close is a CustomEvent whose detail.reason is escape, back, outside, focus-out, close or anchor-hidden; the product sets open to false from it. Slots: default and actions. The container is a popover="manual" element with role="dialog" and no aria-modal in the shadow root, so it is in the top layer (above an open modal <dialog> when the element is a descendant of that dialog, which it must be to be operable there, POP-25); manual and not auto, because the popover implements its own light dismiss so that close carries a reason and so that an overlay opened from inside it is not closed by the browser's own rule. It is placed with CSS anchor positioning where the browser has it and by measuring otherwise, and follows its anchor with the shared FollowController. The container and the arrow are one painted shape, so the elevation's ring and shadows run round both. Focus order (POP-20): the top layer does not change the tab order, so where the element does not directly follow its opener in the document the popover moves focus itself — Tab on the opener while open goes to the first tabbable element of the popover, Shift+Tab from that element to the opener, and Tab from the last to the element after the opener. The autofocus attribute on a slotted element marks initial focus; the container has tabindex="-1". A press outside also reaches the element under it. Under forced-colors: active the painted shape takes a CanvasText outline (POP-32). The inline attribute, for tests, draws the open container in place. Snapshot harness: a stage of 480 × 480 with a medium outlined tanrei-button labelled "Storage" at its centre and the popover open against it; width-anchor gives the button a width of 240; the screenshot is the stage. dividerVisibility is the divider-visibility attribute (auto, always, scroll); auto reads --tanrei-comp-popover-divider-visibility from the cascade with a style query, and where the browser has none the popover writes the name it read on its clip when it lays out. Compact (POP-39 to POP-42): a media query cannot read a custom property, so the popover reads the resolved comp.popover.compact.window-width through a probe and matches it with matchMedia('(width < …px)'), presenting again at once on change. As a sheet the container is a native <dialog> opened with showModal() in the shadow root, in place of the popover="manual" element: in the top layer over ::backdrop (the scrim), the rest of the page inert and held from scrolling. The slotted content is the same, and the element that had focus is focused again across the change. The keyboard's cover is read from visualViewport. The inline attribute keeps the anchored presentation in every window. Snapshot harness: the compact- fixtures are the real overlay in a 360 × 640 window, and the screenshot is the window. | A modifier on the anchor: .tanreiPopover(isPresented: Binding<Bool>, headline:, accessibilityLabel:, placement:, offset: CGFloat? = nil, crossOffset:, arrow: Bool? = nil, width:, inset:, initialFocus:, closable:, closeLabel:, onClose:, actions:, content:); a separate opener is named with .tanreiPopoverOpener(_:) on that view. Dismissal writes false to the binding and then calls onClose with the reason. Drawn in an overlay above the window's content, anchored with anchor preferences, as the tooltip is, not with .popover: the system's is modal at compact widths and its arrow, placement and motion cannot be given by tokens. Initial focus is .defaultFocus or an @AccessibilityFocusState the product passes; the container is an accessibility container with the header trait on the headline, placed after the opener with accessibilitySortPriority; the iOS escape gesture is .accessibilityAction(.escape). A press outside does not reach what is under it. Snapshot harness: the anchor and the open popover in a fixed 480 × 480 frame. Not built on this platform yet: dividerVisibility (auto, always, scroll; POP-38) — the dividers still follow the scroll position, as scroll does. compactPresentation is compactPresentation: TanreiPopoverCompactPresentation = .sheet; in a horizontally compact size class the sheet form is a sheet the popover draws with the bottom sheet's tokens, as SwiftUI's own popover adapts (presentationCompactAdaptation), not .popover. | Compose idiom, as the menu: TanreiPopover(visible, onDismissRequest, headline, contentDescription, placement, offset: Dp? = null, crossOffset, arrow: Boolean? = null, width, inset, initialFocus: TanreiPopoverInitialFocus, initialFocusRequester: FocusRequester? = null, closable, closeLabel, actions, content) inside the anchor's Box, shown in a Popup with a PopupPositionProvider of its own. visible rather than calling it only while open, so that the close motion can run. The caller closes it, so close is onDismissRequest and it carries the reason. The Popup is focusable while initialFocus is popover, so back reaches it; because a focusable popup is a window of its own, the popover moves focus itself at its ends (POP-20): Tab from its last element clears the popup's focus, asks the anchor's FocusManager to move next and reports focus-out. paneTitle is the headline or contentDescription. A press outside does not reach what is under it. Snapshot harness: the anchor and TanreiPopoverContent in place in a 480 × 480 dp box; the popup itself is covered by instrumented tests. Not built on this platform yet: dividerVisibility (auto, always, scroll; POP-38) — the dividers still follow the scroll position, as scroll does. compactPresentation is compactPresentation: TanreiPopoverCompactPresentation = Sheet; the sheet form is shown in a Dialog window whose own dim is off, Tanrei drawing the bottom sheet's scrim. | Flutter idiom, as TanreiMenuAnchor: TanreiPopover(controller:, placement:, offset:, crossOffset:, arrow:, width:, inset:, initialFocus:, headline:, semanticLabel:, showCloseButton:, closeButtonSemanticLabel:, onClose:, actions: […], content:, builder:) wraps the anchor that builder returns; TanreiPopoverController.open() and .close() are open, and onClose carries the reason. Shown with an OverlayPortal and a CompositedTransformFollower: the portal's child stays where it is declared in the widget, focus and semantics trees, so the popover follows its anchor in traversal and reading order without more (POP-20). Outside presses are a TapRegion whose group the overlays opened from inside it join (POP-24); a press outside also reaches what is under it. autofocus: true on a descendant marks initial focus; Semantics(scopesRoute: false, namesRoute: true, label:) names the container. Snapshot harness: the anchor and the open popover in a 480 × 480 box. Not built on this platform yet: dividerVisibility (auto, always, scroll; POP-38) — the dividers still follow the scroll position, as scroll does. compactPresentation is compactPresentation: TanreiPopoverCompactPresentation.sheet; the sheet form is a modal route drawn with the bottom sheet's tokens. | <Popover visible anchorRef openerRef? onClose headline …>{content}</Popover>, presented on the overlay layer that TanreiProvider renders (not a Modal, which would make the rest of the screen unreachable) and placed by measuring the anchor (measureInWindow), again on layout, scroll and keyboard events; Android's back button (BackHandler) is close with the reason back, and the iOS escape gesture is onAccessibilityEscape. Controlled: the caller sets visible to false from onClose. accessibilityViewIsModal stays false; the container is given accessibility focus when it opens and the opener when it closes. A hardware keyboard's Tab order follows the native view order, which the overlay layer puts last: the README states that the popover is reached from its opener by the focus it takes on opening, and returns to the opener on leaving. A press outside closes the popover and does not reach what is under it. Follows the app's direction where an ancestor's is not known. Not built on this platform yet: dividerVisibility (auto, always, scroll; POP-38) — the dividers still follow the scroll position, as scroll does. compactPresentation is a string prop ('sheet' | 'own'); the sheet form is presented in a Modal whose own dim and transition are off, with accessibilityViewIsModal true. |
トークン
| トークン | 参照先 | 解決値(ライト) |
|---|---|---|
--tanrei-comp-popover-actions-arrangement | — | 0 |
--tanrei-comp-popover-actions-gap | sys.space.space200 | 16px |
--tanrei-comp-popover-actions-margin-block-start | sys.space.space200 | 16px |
--tanrei-comp-popover-actions-variant | — | 1 |
--tanrei-comp-popover-arrow-height | sys.space.space75 | 6px |
--tanrei-comp-popover-arrow-inset | sys.space.space100 | 8px |
--tanrei-comp-popover-arrow-visible | — | 0 |
--tanrei-comp-popover-arrow-width | sys.space.space150 | 12px |
--tanrei-comp-popover-close-button-variant | — | 1 |
--tanrei-comp-popover-compact-window-width | sys.breakpoint.medium | 600px |
--tanrei-comp-popover-container-color | sys.color.surface-bright | |
--tanrei-comp-popover-container-elevation | sys.elevation.level2 | 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04) |
--tanrei-comp-popover-container-gap | sys.space.space150 | 12px |
--tanrei-comp-popover-container-max-height | — | 480px |
--tanrei-comp-popover-container-max-width | — | 400px |
--tanrei-comp-popover-container-min-width | — | 160px |
--tanrei-comp-popover-container-padding-block | sys.space.space200 | 16px |
--tanrei-comp-popover-container-padding-inline | sys.space.space200 | 16px |
--tanrei-comp-popover-container-shape | sys.shape.corner.medium | 8px |
--tanrei-comp-popover-content-color | sys.color.on-surface | |
--tanrei-comp-popover-divider-color | sys.color.outline-variant | |
--tanrei-comp-popover-divider-visibility | sys.behavior.divider-visibility | 0 |
--tanrei-comp-popover-divider-width | sys.border.hairline | 1px |
--tanrei-comp-popover-header-gap | sys.space.space100 | 8px |
--tanrei-comp-popover-headline-color | sys.color.on-surface | |
--tanrei-comp-popover-motion-close-duration | sys.motion.role.appear.anchored.exit | 250ms |
--tanrei-comp-popover-motion-close-easing | sys.motion.easing.exit | cubic-bezier(0.3, 0, 1, 1) |
--tanrei-comp-popover-motion-open-duration | sys.motion.role.appear.anchored.enter | 300ms |
--tanrei-comp-popover-motion-open-easing | sys.motion.easing.enter | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-popover-offset | sys.space.space50 | 4px |
--tanrei-comp-popover-window-margin | sys.space.space100 | 8px |
--tanrei-comp-popover-headline-typescale | sys.typescale.heading-6 | 500 14px/1.4286 |
フィクスチャ
基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。
basic
open: true label: Storage 480 × 480 のウィンドウに単独で描画headline
open: true headline: Storage 480 × 480 のウィンドウに単独で描画closable
open: true headline: Storage closable: true closeLabel: Close storage 480 × 480 のウィンドウに単独で描画closable-untitled
open: true label: Storage closable: true closeLabel: Close storage 480 × 480 のウィンドウに単独で描画actions
open: true headline: Empty the trash? closable: true closeLabel: Close items: 2 480 × 480 のウィンドウに単独で描画single-action
open: true headline: Storage items: 1 480 × 480 のウィンドウに単独で描画actions-stacked
open: true headline: Share this page items: 2 480 × 480 のウィンドウに単独で描画action-variants
open: true headline: Empty the trash? items: 2 480 × 480 のウィンドウに単独で描画scrolling
open: true headline: What changed closable: true closeLabel: Close what changed items: 1 480 × 480 のウィンドウに単独で描画inset-off
open: true inset: false headline: Storage 480 × 480 のウィンドウに単独で描画width-anchor
open: true width: anchor label: Storage 480 × 480 のウィンドウに単独で描画placement-bottom
open: true placement: bottom label: Storage 480 × 480 のウィンドウに単独で描画placement-bottom-end
open: true placement: bottom-end label: Storage 480 × 480 のウィンドウに単独で描画placement-top
open: true placement: top label: Storage 480 × 480 のウィンドウに単独で描画placement-top-start
open: true placement: top-start label: Storage 480 × 480 のウィンドウに単独で描画placement-top-end
open: true placement: top-end label: Storage 480 × 480 のウィンドウに単独で描画placement-start
open: true placement: start label: Storage 480 × 480 のウィンドウに単独で描画placement-start-top
open: true placement: start-top label: Storage 480 × 480 のウィンドウに単独で描画placement-start-bottom
open: true placement: start-bottom label: Storage 480 × 480 のウィンドウに単独で描画placement-end
open: true placement: end label: Storage 480 × 480 のウィンドウに単独で描画placement-end-top
open: true placement: end-top label: Storage 480 × 480 のウィンドウに単独で描画placement-end-bottom
open: true placement: end-bottom label: Storage 480 × 480 のウィンドウに単独で描画placement-auto
open: true placement: auto label: Storage 480 × 480 のウィンドウに単独で描画arrow
open: true arrow: true label: Storage 480 × 480 のウィンドウに単独で描画arrow-bottom
open: true placement: bottom arrow: true label: Storage 480 × 480 のウィンドウに単独で描画arrow-top-end
open: true placement: top-end arrow: true label: Storage 480 × 480 のウィンドウに単独で描画arrow-end
open: true placement: end arrow: true label: Storage 480 × 480 のウィンドウに単独で描画arrow-actions
open: true placement: bottom arrow: true headline: Empty the trash? closable: true closeLabel: Close items: 2 480 × 480 のウィンドウに単独で描画offset
open: true offset: 16 crossOffset: 24 label: Storage 480 × 480 のウィンドウに単独で描画rtl
open: true headline: إفراغ سلة المهملات؟ closable: true closeLabel: إغلاق direction: rtl items: 2 480 × 480 のウィンドウに単独で描画rtl-arrow-end-top
open: true placement: end-top arrow: true label: التخزين direction: rtl 480 × 480 のウィンドウに単独で描画rtl-ltr-headline
open: true placement: bottom-end headline: v2.1 beta closable: true closeLabel: إغلاق direction: rtl 480 × 480 のウィンドウに単独で描画compact
open: true headline: Empty the trash? items: 2 360 × 640 のウィンドウに単独で描画compact-own
open: true compactPresentation: own label: Storage 360 × 640 のウィンドウに単独で描画生成元 specs/popover.spec.json, tokens/comp/popover.tokens.json, design/tokens/
- ポップオーバーは、トップレイヤーにある
popover="manual"の要素です。モーダルの<dialog>の中で操作できるようにするには、その dialog の子孫に置きます。 - コンパクトなウィンドウのシートは、
showModal()で開くネイティブの<dialog>で、スクリムはその::backdropです。 - 外側を押すとポップオーバーは閉じ、押した位置にある要素にもその操作が届きます。
- Web には固有の戻る操作がないので、
closeが理由backを伝えることはありません。 anchorElementとopenerElementは、id で届かない要素(別のシャドウルートの中の要素など)を指定するプロパティです。arrowは未設定、true、false のいずれかです。属性は、なし、arrow、arrow="false"で表します。- 開いたときにフォーカスを受け取る要素には、
autofocusを付けます。 - 強制カラーモードでは、コンテナと矢印にテキストの色の輪郭線が付きます。
| 観点 | ツールチップ | ポップオーバー | メニュー | ダイアログ |
|---|---|---|---|---|
| 開くきっかけ | ホバー、キーボードフォーカス、長押し | オープナーを押す | アンカーを押す | プロダクト |
| 入るもの | アンカーの説明 | ユーザーが操作するコンテンツ | 項目だけ | 判断 |
| フォーカス | 受け取りません | 受け取り、戻します | 受け取り、戻します | 受け取り、保ちます |
| ページをふさぐか | いいえ | いいえ(コンパクトなウィンドウのシートでは、はい) | いいえ | はい |
- コントロールが何であるかを伝えるだけで、押せるものが中にないなら、ツールチップです。
- コマンドや選択肢のリストなら、メニューです。
- 答えなければ先に進めないなら、ダイアログです。
- それ以外は、ポップオーバーです。
- コントロールを押して開き、同じコントロールをもう一度押したら閉じます。
- コンテンツは短くします。1 つの作業で、フィールドは多くても数個です。
- ポップオーバーが大きく、外側の押せる場所を覆ってしまうときは、
closableを付けます。Escape のないタッチのためにも付けます。 - ユーザーが入力しているフィールドの下に出すサーフェスには、
width="anchor"とinitialFocus="anchor"を使います。 compactPresentationをownにするのは、コンパクトなウィンドウでも属するものの横にいなければならないポップオーバー(説明する語のすぐ横に出す注など)だけにします。- アクションのラベルは短くします。ポップオーバーより幅の広いラベルは折り返しません。
- ホバーやフォーカスでポップオーバーを開かない。タッチでは届きません。ポインターが通っただけでフォーカスを奪うサーフェスは、抜け出せない罠になります。
- 外側をうっかり 1 回押しただけで失われて困るものを、ポップオーバーに入れない。長いフォームは、ダイアログかシートに入れます。
- ポップオーバーの中に、ポップオーバーを 2 つ以上重ねない。1 段増えるごとに、Escape を押す回数が 1 回増えます。
- 先に進む前に答えが必要なものに、ポップオーバーを使わない。それはダイアログです。
- ロール
dialog- ラベル付け
- The popover is a dialog that is not modal — a modal one while it is a sheet in a compact window (POP-40) — named by its headline, or by label where it draws none. The headline is also exposed as a heading. The opener keeps its own name and role and exposes that it opens a dialog and whether it is open. The close button is named by closeLabel and each action by its own label; confirming and dismissive are not announced, and the actions are read in the order they are placed (BGR-15). The arrow and the dividers are decorative. The popover is not its anchor's description.
- 最小ターゲット
- 見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px
キーボード操作
| キー | 動作 |
|---|---|
| Enter / Space on the opener | The product opens the popover; focus moves into it (to the element the product marked, otherwise to the container). Pressed again while it is open, the product closes it. |
| Tab / Shift+Tab | Move through the popover's close button, its content and its actions; the popover comes directly after its opener in the order. Shift+Tab from its first element returns to the opener and leaves it open; Tab from its last element moves on to what follows the opener and closes it. Focus is never kept in. As a sheet in a compact window (POP-40) it keeps focus in: Tab and Shift+Tab wrap. |
| Escape | Closes the popover and returns focus to its opener; with overlays opened from inside it, only the topmost. It goes no further: a dialog the popover stands in stays open. As a sheet, a press on the scrim closes it too. |
| Enter / Space | Activate the focused close button (close, reason close) or action (its click; the popover does not close by itself, POP-17). The popover adds no default action to Enter. |
備考
- The popover has no target of its own; its close button and its actions are Tanrei Buttons, which meet the minimum target themselves (BTN-5). comp.popover.actions.gap keeps each action's whole target in a column in the default system. The opener is the product's control and keeps its own target.
- Tooltip, popover, menu, dialog — which one. A tooltip describes its anchor, opens on hover and focus, never takes focus and holds nothing a person can operate (TIP-13). A popover is opened by a press, takes focus and holds content a person operates. A menu is a list of commands or choices, moved through with the arrow keys; it holds only its items. A dialog stops the task until it is answered. A note that needs a link or a button is a popover, never a tooltip.
- Never open a popover on hover or on focus (POP-18): what appears that way must be dismissible, hoverable and persistent (WCAG 1.4.13) and cannot be reached by touch, and a surface that takes focus because the pointer passed is a trap. Open it from a press.
- Focus: the popover is an operated overlay that is not modal. It moves focus in when it opens and returns it to the opener when it closes, but does not keep it: Tab leaves it, which closes it. An open surface nobody is in would cover content the keyboard user has moved on to, and a pointer user closes it by pressing elsewhere; focus leaving is the keyboard's form of the same thing. This is the menu's rule, the docked picker's and the docked search view's.
- Initial focus goes to the container unless the product marks an element (POP-19), as for the dialog: assistive technology reads the popover's name first, and a field that takes focus by itself would raise the on-screen keyboard over a popover the person has not read. Mark the first field of a short form.
- initialFocus anchor is for a surface under a field the person is typing in (suggestions, a format hint with a link). Focus stays in the field, so the field's own keys keep working; the popover is still next in the Tab order, and still closes when focus leaves both.
- Reading order: the popover follows its opener for a screen reader as it does for Tab (POP-20). On the web, place the element directly after its anchor in the document where the layout allows; where it cannot be, the opener's aria-controls and the focus that moves in carry a person there.
- A press outside closes the popover. Do not put in a popover what a person would lose by one stray press: a long form belongs in a dialog or a sheet. Where open is a plain value the product may also answer close with the reason outside by leaving the popover open; do that only with a visible way to close it (closable, or a dismissive action).
- Keep nesting shallow: an overlay opened from a popover works at any depth (POP-24), but each level is one more thing Escape must be pressed for. One popover inside a popover is the most a product should need; a debug build reports a third.
- Not modal means the rest of the page can still be read and scrolled, and a screen reader's cursor can leave the popover without closing it (POP-21). Nothing a person must answer before going on belongs in a popover.
- Text size and small windows: at 200% text the popover wraps, grows to its limits and scrolls. In a compact window it is a sheet across the window's width (POP-39); one that keeps its own presentation is, at a window 320 wide, the window's width less the margins (POP-43). It never needs scrolling in two directions.
- A compact window (POP-39 to POP-42): there a popover takes the bottom sheet's appearance, as every popover and every dialog of Tanrei does, and becomes a modal dialog — a scrim, everything behind it inert, focus kept in, Escape, back or a press on the scrim to close it. Modal because the sheet form is what the platforms make of a popover on a phone, and they make it modal: iOS adapts a popover to a sheet in a compact size class (UIPopoverPresentationController's adaptive sheet; SwiftUI's presentationCompactAdaptation), and Apple's guidelines present a popover's content in a compact view as a modal sheet; React Spectrum presents a popover as a modal on mobile; Material's bottom sheet that stands in for a menu or a dialog on mobile is a modal one. A surface over a scrim that left the page operable would show one thing and do another (WAI-ARIA APG, dialog: a modal dialog makes what is behind it inert). The opener still says it opens a dialog. Set compactPresentation to own for a popover that must stay beside what it belongs to; initialFocus anchor always keeps it there, because the field being typed in must stay usable.
- Forced colors (POP-32): the container gains an edge in the text color, because its shadow is removed. Contrast in the default system: on-surface on surface-bright is held to 4.5:1 by the token invariants; the container is told from the page by its elevation's ring and shadow, which are not held to a contrast of their own, as for the menu.
生成元 specs/popover.spec.json, tokens/sys/measure.tokens.json
- コンテナは、モーダルではないダイアログです。
headlineまたはlabelが名前になります。外側のものを隠したり、操作不能にしたりしません。 - コンパクトなウィンドウでシートとして表示されているときは、コンテナはモーダルなダイアログです。外側はすべて操作不能になり、Tab と Shift+Tab は中で循環します。閉じると、フォーカスはオープナーに戻ります。
- オープナーは、ダイアログを開くことと、そのダイアログが開いているかどうかを伝えます。オープナー自身の名前とロールは変わりません。
- 開くと、利用者側で印を付けた要素に、なければコンテナにフォーカスを移します。そのため、ポップオーバーの名前が最初に読み上げられます。コンテナのフォーカスインジケーターは、キーボードで開いたときだけ表示します。
- ポップオーバーは、文書のどこに書かれていても、Tab の順序ではオープナーのすぐ後に来ます。最後の要素で Tab を押すと、フォーカスはオープナーの次の要素に進み、ポップオーバーは閉じます。フォーカスを閉じ込めることはありません。
- Escape は、いちばん上のオーバーレイだけを閉じ、それより先には届きません。ポップオーバーが置かれているダイアログは、開いたままです。
- 閉じるとき、フォーカスがポップオーバーの中にあれば、オープナーに戻します。ユーザーがフォーカスをほかへ動かしていた場合は、そのままにします。
- メニューやツールチップなど、ポップオーバーの中から開いたオーバーレイは、ポップオーバーの一部です。その中を押したり、その中にフォーカスを移したりしても、ポップオーバーは閉じません。
- テキストを 200% にすると、ポップオーバーの中のテキストは折り返し、ポップオーバーは上限まで大きくなり、それを超えるとスクロールします。コンパクトなウィンドウでは、ウィンドウの幅いっぱいのシートになります。自分の表示を保つポップオーバーは、幅 320 のウィンドウでは、ウィンドウの幅から余白を引いた幅になります。
- ポップオーバーに
headlineまたはlabelを付けます。closableのときはcloseLabelも付けます。 - オープナーが押されたら
openを設定し、closeを受け取ったら false にします。 - Web では、できるだけポップオーバーの要素を文書の中でオープナーのすぐ後に置きます。スクリーンリーダーの読み上げ順が、Tab の順序と同じになります。
- 短いフォームでは、最初のフィールドに、開いたときにフォーカスを受け取る印を付けます。テキストだけのポップオーバーには付けません。