Tooltip
A hint that opens on hover AND keyboard focus, with role="tooltip" and aria-describedby.
6 个框架中级
A click-triggered panel anchored to its button, with Escape and outside-click dismiss.
<!--
Click-triggered, so the trigger is a real <button> and the relationship is
spelled out: aria-expanded says whether the panel is showing, aria-controls
says which panel. aria-haspopup="dialog" - NOT "menu": this holds prose, and
promising a menu makes a screen reader announce arrow-key navigation that
does not exist here.
The wrapper is position: relative so the panel anchors to the trigger rather
than the viewport. That is the whole positioning story - no floating library,
no measurement.
-->
<div class="pop">
<button
class="pop__trigger"
type="button"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="pop-basic-panel"
>
Storage details
</button>
<div class="pop__panel" id="pop-basic-panel" role="dialog" aria-label="Storage details" hidden>
<p class="pop__text">
You are using <strong>18.2 GB</strong> of 50 GB. Old versions are pruned
after 30 days.
</p>
</div>
</div>
<script>
document.querySelectorAll('.pop').forEach(function (root) {
var trigger = root.querySelector('.pop__trigger');
var panel = root.querySelector('.pop__panel');
function setOpen(open) {
panel.hidden = !open;
trigger.setAttribute('aria-expanded', String(open));
}
trigger.addEventListener('click', function () {
setOpen(trigger.getAttribute('aria-expanded') !== 'true');
});
// Escape closes and gives focus back. A popover the keyboard can open but
// not leave is worse than one that never opened.
root.addEventListener('keydown', function (event) {
if (event.key !== 'Escape') return;
setOpen(false);
trigger.focus();
});
// Outside click dismisses - mousedown, not click, so the popover is gone
// before the thing underneath reacts.
document.addEventListener('mousedown', function (event) {
if (!root.contains(event.target)) setOpen(false);
});
});
</script>| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
label必填 | string | - | 加载时朗读的无障碍标签。 |
children必填 | ReactNode | - | 在组件内部渲染的内容。 |
className | string | - | 合并到根元素上的额外类名。 |
A popover is a modal's opposite number: it floats over the page without taking it away, so there is no focus trap and no scroll lock here - and there should not be. What it still owes is the rest of the contract: `aria-expanded` bound to state, `aria-controls` naming the panel, Escape to close, and focus handed back to the trigger so Tab does not resume from a node that no longer exists. `aria-haspopup="dialog"`, not `"menu"` - this holds prose, and promising a menu makes a screen reader announce arrow-key navigation that does not exist. The outside-click listener uses `mousedown` rather than `click` so the panel is gone before the element underneath reacts.