Home / Snippets / UI Components /

Popover

Native HTML popover with CSS styling — dismisses on outside click, no JavaScript needed.

Native Popover API

Dismiss by clicking outside or pressing Escape. No JS required.

New Feature
uino-js

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.