Basic Popover
A click-triggered panel anchored to its button, with Escape and outside-click dismiss.
6 個のフレームワーク中級
An anchored popover with a pointing tail that says which control it belongs to.
<!--
Same popover, plus a tail that says which control it belongs to. Worth it when
several triggers sit close together and "the panel under the button" is
ambiguous.
The arrow is a rotated square, not a border triangle. A CSS triangle is a
solid colour and cannot carry the panel's 1px border across its two visible
faces, so on a bordered panel it reads as a blob stuck to the edge. Half a
rotated square, clipped by the panel, does.
It is aria-hidden. It is a drawing of a relationship that aria-controls has
already stated properly.
-->
<div class="apop">
<button
class="apop__trigger"
type="button"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="apop-panel"
>
Sync status
</button>
<div class="apop__panel" id="apop-panel" role="dialog" aria-label="Sync status" hidden>
<span class="apop__arrow" aria-hidden="true"></span>
<p class="apop__text">
Last synced 4 minutes ago. Changes upload automatically when you are back
online.
</p>
</div>
</div>
<script>
document.querySelectorAll('.apop').forEach(function (root) {
var trigger = root.querySelector('.apop__trigger');
var panel = root.querySelector('.apop__panel');
function setOpen(open) {
panel.hidden = !open;
trigger.setAttribute('aria-expanded', String(open));
}
trigger.addEventListener('click', function () {
setOpen(trigger.getAttribute('aria-expanded') !== 'true');
});
root.addEventListener('keydown', function (event) {
if (event.key !== 'Escape') return;
setOpen(false);
trigger.focus();
});
document.addEventListener('mousedown', function (event) {
if (!root.contains(event.target)) setOpen(false);
});
});
</script>| Prop | 型 | デフォルト | 説明 |
|---|---|---|---|
label必須 | string | - | 読み込み中に読み上げられるアクセシブルなラベル。 |
children必須 | ReactNode | - | コンポーネントの内部にレンダリングされるコンテンツ。 |
className | string | - | ルート要素にマージされる追加クラス。 |
Worth reaching for when several triggers sit close together and "the panel under the button" is ambiguous. The arrow is a rotated square with only `border-l` and `border-t`, not a CSS border triangle: a triangle is one solid colour and cannot carry the panel's 1px border across its two visible faces, so on a bordered panel it reads as a blob stuck to the edge. Rotated 45°, those two borders become the tail's outward-facing edges and continue the panel's outline - give it all four and you get a diamond seam. Recolour the arrow WITH the panel in dark mode or it stays a white chip on a dark card, which is the most visible way to get this wrong. It is `aria-hidden`: it draws a relationship `aria-controls` has already stated properly.