Home / Snippets / UI Components /
Popover
Native HTML popover with CSS styling — dismisses on outside click, no JavaScript needed.
Dismiss by clicking outside or pressing Escape. No JS required.
Quick implementation
/* HTML:
<button type="button" popovertarget="my-popover">Show info</button>
<div id="my-popover" class="css-popover" popover>
<strong>Popover title</strong>
<p>Some helpful content here.</p>
<button type="button" popovertarget="my-popover" popovertargetaction="hide">Close</button>
</div> */
.css-popover {
position: absolute;
width: 16rem;
padding: 1rem 1.25rem;
background: var(--card);
border: 1px solid oklch(0.3 0.02 260);
border-radius: 0.5rem;
box-shadow: 0 8px 24px oklch(0 0 0 / 0.4);
color: var(--text);
font-size: 0.9rem;
line-height: 1.5;
margin: 0;
}
/* Transparent backdrop — light dismiss still works */
.css-popover::backdrop {
background: transparent;
}
/* Style the open state */
.css-popover:popover-open {
display: block;
}
/* Animate entry (progressive enhancement) */
@supports (transition-behavior: allow-discrete) {
.css-popover {
opacity: 0;
translate: 0 -0.5rem;
transition:
opacity 0.2s ease,
translate 0.2s ease,
display 0.2s allow-discrete,
overlay 0.2s allow-discrete;
}
.css-popover:popover-open {
opacity: 1;
translate: 0 0;
}
@starting-style {
.css-popover:popover-open {
opacity: 0;
translate: 0 -0.5rem;
}
}
}
@media (prefers-reduced-motion: reduce) {
.css-popover {
transition: none;
}
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building modern, accessible UI components.
Goal: A native HTML Popover API component styled with CSS — tooltip-style content that opens on button click, dismisses on outside click or Escape, with a smooth CSS entry animation. No JavaScript required.
Technical constraints:
- Use the popover attribute on the content element and popovertarget on the trigger button.
- Style the open state with :popover-open pseudo-class.
- Use ::backdrop { background: transparent } to keep light-dismiss without a dark overlay.
- Animate entry using @starting-style and transition-behavior: allow-discrete, wrapped in @supports so it degrades gracefully.
- Use oklch() for all color values throughout.
- Wrap all transitions in @media (prefers-reduced-motion: reduce).
- Background and border must use dark design tokens (var(--card), oklch()).
Framework variant (pick one):
A) Vanilla HTML + CSS — button with popovertarget, div with popover attribute.
B) React component — wrap in a controlled component that uses the Popover API via a ref; accept trigger (ReactNode) and children props.
Edge cases to handle:
- Popover positioned near viewport edges must not overflow — use anchor positioning or clamp.
- Multiple popovers on the same page should not conflict — each needs a unique ID.
- Keyboard navigation: Escape must close the popover; focus should return to the trigger.
- Fallback for browsers without Popover API support: display:none toggle via JS.
Return HTML + CSS.
Why this matters in 2026
The Popover API landed in all major browsers in 2024 and eliminates an entire class of JavaScript that developers previously wrote from scratch: toggle logic, outside-click listeners, Escape-key handlers, and focus traps for informational overlays. Before this API, even a simple tooltip-style popover required dozens of lines of JS and often introduced accessibility bugs like focus getting trapped or lost. The native implementation handles light-dismiss, Escape-key closure, and top-layer rendering automatically, making it both more accessible and more performant than any hand-rolled alternative. In 2026, reaching for JavaScript for this use case is unnecessary technical debt.
The logic
Adding the popover attribute to any element opts it into the Popover API — the browser hides it by default and promotes it to the top layer when opened. The trigger button connects to the popover via popovertarget, matching the popover's id; no JavaScript event listeners are needed. The ::backdrop pseudo-element is styled with background: transparent so the light-dismiss behavior (clicking outside closes the popover) works without the visual overlay of a modal. For the entry animation, @starting-style defines the initial state before the popover transitions to :popover-open, and transition-behavior: allow-discrete enables animating the display property itself.
Accessibility & performance
The Popover API provides correct ARIA semantics automatically — the trigger receives aria-expanded and the popover element is announced as a non-modal region by screen readers without additional attributes. Focus management is built in: when the popover closes, focus returns to the invoking element, satisfying WCAG 2.5 focus requirements. The @supports (transition-behavior: allow-discrete) wrapper ensures the animation is strictly progressive enhancement — browsers that do not support it still show the popover instantly. Rendering in the top layer avoids any z-index stacking context issues, which were the leading source of popover-related visual bugs in JavaScript implementations.