Toast notifications are transient status messages. Unlike menus, they should not close when a user interacts with other parts of the page. The popover="manual" state is ideal because it lacks "light-dismiss" behavior and allows multiple notifications to coexist.
Implementation Guidelines
- MANDATORY: Use popover="manual" so the notification stays visible until explicitly closed or timed out by a script.
- DO use a container to manage the stacking of multiple toasts. Since popovers in the Top Layer ignore parent z-index, you must position them individually or within a common layout group.
- DO use sibling-index() to add margin between toast notifications so that items lower in the stack are visible.
- DO provide an explicit "Close" button within the toast using popovertargetaction="hide".
- DO use JavaScript for auto-dismissal timers (e.g., calling hidePopover() after 3000ms).
- DO utilize transition-behavior: allow-discrete to animate the entry and exit from the Top Layer.
Fallback Strategies
Fallbacks & browser support for Popover
The Popover API is mostly progressive enhancement, but its defining behaviors — top-layer promotion, light-dismiss, and popovertarget invocation — have no CSS-only equivalent. Older browsers need a polyfill, or a manual fallback if you would rather not ship one.
Polyfill: To support the popover attribute in older browsers, conditionally load @oddbird/popover-polyfill. MANDATORY: Feature detect by checking for the popover property on HTMLElement.prototype, and load the polyfill only when native support is missing — do NOT load it unconditionally.
With a bundler or import map:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Without a bundler, import from a CDN inside a <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Styling caveat: The polyfill cannot define the real :popover-open pseudo-class, so it applies a .\:popover-open class instead. MANDATORY: Combine the two with :is() or :where(), otherwise browsers that lack :popover-open discard the entire rule:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Alternatively, for a legacy fallback without a polyfill, use position: fixed and manually calculate coordinates via getBoundingClientRect() or rely on default positioning with inset: auto if that's acceptable for the use case.
sibling-index()
- Guidance: If sibling-index() is not supported, use the
+operator to add margin manually. I.e.popover + popover { margin-top: 1rem }
anchor-positioning
- Guidance: Use the CSS Anchor Positioning Polyfill. For a non-polyfill fallback, default the tooltip to a fixed position at the bottom of the viewport using
@supports not (anchor-name: --foo).
transition-behavior
- Guidance: If transition-behavior is not supported, use JavaScript to add animation via classes as the toast element transitions in and out.