20 CSS Popovers

17 Pure CSS3 Light JSPublished

20 hand-coded CSS popovers built on the native Popover API and CSS Anchor Positioning — no positioning library, no z-index wrangling. Covers declarative show and hide with popovertarget, auto versus manual dismissal, tethering panels with position-anchor, position-area placement, flip fallbacks near the viewport edge, entry and exit animation with @starting-style, and real surfaces like listboxes, profile flyouts and filter menus. Scoped under .pop-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

Related21 CSS Tooltips22 CSS Dropdown Menu Designs36 CSS Modals20 CSS Custom Select Dropdowns20 Tailwind CSS Popovers

Course pickPartner: Zero to Mastery

Affiliate link

Want to build production-ready CSS layouts, animations and UI from scratch?

You just browsed 20 popovers. Learn the Flexbox, Grid and animation thinking behind them, so the next component starts in your head, not in a copy-paste.

10% off at checkout with code
Check ZTM Course

Every ZTM course in one membership: $25/month, paid yearly at $299 · 30‑day money-back guarantee

The CSS Bootcamp. We may earn a commission, at no extra cost to you.

01. Basic Popover Show and Hide (opens in a new tab)

Published

Pure CSS
The reference build the rest of the collection reuses: one button carrying popovertarget, one element carrying the popover attribute, and no script at all. The browser promotes the panel to the top layer, closes it when you click outside or press Escape, and returns focus to the button — behaviour that used to take a few hundred lines of listener plumbing and a z-index audit.

02. Auto vs Manual Popover Behavior (opens in a new tab)

Published

Pure CSS
Two panels open at once, side by side, so the difference between the auto and manual states is something you can click rather than read. Auto light-dismisses, answers Escape, and forces other auto popovers closed; manual ignores all three and waits for an explicit hide. The same board also wires the three popovertargetaction values to separate buttons.

03. Basic Anchored Popover (opens in a new tab)

Published

Pure CSS
The two features compose but ship separately: popover handles the top layer and dismissal, anchor positioning handles where the panel lands. Name the trigger with anchor-name, point the panel at it with position-anchor, pick a side with position-area, and the entire measure-and-reposition dance that positioning libraries exist for disappears into four declarations.

04. Discrete Entry and Exit Animations (opens in a new tab)

Published

Pure CSS
Animating a popover takes three declarations and almost everyone ships two of them. @starting-style supplies the entry values, transition-behavior: allow-discrete lets display animate, and transitioning overlay keeps the element in the top layer while it leaves. Skip the third and the panel drops out of the top layer mid-exit and appears to teleport behind the page.

05. Custom Backdrop and Light Dismiss (opens in a new tab)

Published

Pure CSS
Every popover gets a ::backdrop box behind it, and styling it gives you a dimmed page without an overlay div, a scroll lock, or a click listener. Light dismiss stays intact: the backdrop is decoration, not a click shield, so clicking through it still closes an auto popover. Manual popovers get the same box and none of the dismissal.

06. Grid Placement with Position Area (opens in a new tab)

Published

Pure CSS
position-area treats the area around an anchor as a three by three grid and lets you name a cell instead of computing coordinates. This board makes that literal: nine buttons around a fixed anchor card, each opening the same kind of panel in a different cell, so the physical and logical keyword sets stop being an abstraction you have to hold in your head.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

07. Flip and Fallback Positions (opens in a new tab)

Published

Pure CSS
Collision handling used to be the main reason to install a positioning library. position-try-fallbacks replaces it: list the placements you accept, and the browser tries them in order until the panel fits. This demo puts the anchor in a scrollable strip so you can drag it to an edge and watch the panel flip sides in real time rather than take it on faith.

08. Tooltip Arrow Pointing to Anchor (opens in a new tab)

Published

Pure CSS
The bubble is easy and the arrow is the part that breaks. Anchor positioning lets the arrow be positioned against the anchor rather than the bubble, so it stays pointed at the trigger when a fallback flips the panel to the other side. The bubble here is deliberately plain: the lesson is keeping the tail attached through a position change.

09. Match Anchor Width for Dropdowns (opens in a new tab)

Published

Pure CSS
A floating panel that is exactly as wide as the control that opened it reads as part of that control; one that is a little narrower or wider reads as a bug. anchor-size(width) makes the panel size itself from its anchor with no measurement, and min-width: anchor-size(width) gives the forgiving variant that can still grow for long option labels.

10. Submenus and Nested Popovers (opens in a new tab)

Published

Pure CSS
Two auto popovers can be open at once if the browser considers one nested inside the other, and cannot if it does not. Nesting is established by DOM containment or by the popovertarget relationship, and getting it wrong makes the parent menu slam shut the instant the submenu opens. This two-level menu shows the working arrangement and names the rule.

11. Custom Select Listbox Popover (opens in a new tab)

Published

Light JS
Popover hands you the top layer, light dismiss and Escape. It hands you nothing about keyboard semantics, so a listbox built on it still needs roles, a selection state and arrow keys you write yourself. Under thirty lines of vanilla JS covers Up, Down, Home, End and Enter over a real role=listbox, with the panel sized and placed by anchor positioning.

12. Tooltip on Hover and Focus (opens in a new tab)

Published

Light JS
Popover's built-in trigger is click, so a hover tooltip has to call showPopover and hidePopover itself. Under twenty-five lines gets pointer and focus parity, an open delay so the tooltip does not fire while the cursor crosses the element, and no close delay so it gets out of the way instantly. This is the case where the API buys you the least.

13. User Profile Flyout Menu (opens in a new tab)

Published

Pure CSS
The canonical product-header pattern, built with no script: an avatar button anchors an account menu that opens toward the page interior and flips inward when the header sits near the viewport edge. Because an open popover lives in the top layer, the menu is not clipped by the header's own overflow and needs no z-index at all.

14. Date Picker Popover Panel (opens in a new tab)

Published

Pure CSS
A calendar panel tethered to a date field, with the month laid out as a plain CSS grid and no date logic at all. The demo is the panel and its tethering: how a popover attaches to an input, how the grid stays legible at 320px, and why the field needs readonly or a real picker behind it before this ships.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

15. Popover Menu with Checkbox Filters (opens in a new tab)

Published

Pure CSS
The clearest real use of popover=manual: a filter menu the user ticks several boxes in, which must not vanish because a click landed slightly outside it. Manual opts out of light dismiss and Escape, so the panel ships a visible Apply and a Close — the trade the auto behaviour from demo 02 cannot make.

16. Cross Browser Fallback and Polyfill (opens in a new tab)

Published

Light JS
Popover and anchor positioning ship on different timelines, so one feature test is not enough. This demo detects each separately, reports both live, and layers the experience: an absolutely positioned panel that works with no API at all, popover behaviour as the first enhancement, and anchor positioning as the second.

17. Popover vs Dialog Showcase (opens in a new tab)

Published

Pure CSS
One decision rule, shown twice: use dialog with showModal when the user must deal with it, and popover when they can ignore it and carry on. Modal versus non-modal is the entire distinction — focus trapping, page inertness and a click-blocking backdrop all follow from it. The dialog side stays minimal on purpose.

18. Glassmorphic Popover Overlay (opens in a new tab)

Published

Pure CSS
A frosted panel over a rich background, with the solid fallback declared first so an unsupporting engine gets an opaque card rather than unreadable text. The top-layer wrinkle is the interesting part: a popover samples what is painted beneath it, so the same panel can blur differently in the top layer than it does inline.

19. Dark and Light Mode Popover (opens in a new tab)

Published

Pure CSS
One token set on the root, two palettes, and a switch that is pure CSS: a visually hidden checkbox read with :has() rewrites the custom properties for the whole demo, panel included. Because the popover reads the same tokens as the page, the panel never ends up light while its surroundings went dark.

20. Accessible Tooltip with ARIA Roles (opens in a new tab)

Published

Pure CSS
A ledger of what the API gives you and what it does not. It gives the top layer, light dismiss, Escape and focus returning to the trigger. It does not give a role, an accessible name or a described-by relationship — and role=tooltip on a click-triggered panel is actively wrong, because a tooltip is a hover and focus hint, not something you open.
FAQ

Frequently asked questions

How does the HTML Popover API work?
Two attributes and no JavaScript. Put popover on the element you want to float and popovertarget="its-id" on the button that opens it — that is the whole setup. The browser then hands you four behaviours for free that people used to write libraries for: the panel is promoted to the top layer, so it escapes any ancestor with overflow: hidden and ignores z-index entirely; clicking outside closes it; Escape closes it; and focus returns to the trigger on close. The top-layer behaviour is the one that changes how you build. A dropdown inside a scrolling table or a transformed card used to require portalling the panel to the end of the body to stop it being clipped — with popover the element stays where it belongs in your markup and still renders above everything. Pair it with popovertargetaction="show" or "hide" when you want a dedicated open or close control rather than a toggle.
What is the difference between popover auto and popover manual?
popover="auto" (the default, so bare popover means the same thing) is dismissive: clicking outside closes it, Escape closes it, and opening a second auto popover force-closes the first unless they are nested. That is what you want for a menu, a tooltip or a profile flyout — the user should be able to click away and carry on. popover="manual" opts out of all of it. Nothing closes it except an explicit popovertargetaction="hide" button or a hidePopover() call, and several manual popovers can sit open at once. Reach for manual when closing would lose work: a filter menu the user is ticking several boxes in, a persistent panel, an inline editor. The mistake is choosing manual because auto felt unpredictable — if a menu keeps vanishing when you open another, that is nesting being declared wrongly rather than a reason to abandon the light-dismiss behaviour you actually want.
How do I position a popover next to its button with CSS?
CSS Anchor Positioning, which is a separate feature that composes with popover. Give the trigger anchor-name: --my-anchor, give the panel position-anchor: --my-anchor, then place it with position-area — a nine-cell grid around the anchor, so position-area: block-end puts the panel below and block-start inline-end puts it above and to the right. This is what replaces the measure-on-scroll-and-resize loop that Popper and Floating UI exist to run. Two things to know. Anchor names are global, so scope them per component or two instances of the same widget will fight over the same name. And support for anchor positioning lags support for popover itself — a browser can have one without the other, so gate them with separate @supports checks and let the panel fall back to a plain absolutely-positioned element rather than assuming both arrived together.
Why does my popover animation not work?
Because a popover moves between display: none and the top layer, and neither transitions by default. The recipe has three parts and missing any one of them silently breaks it. First, @starting-style declares the values the element animates FROM on first paint — without it there is no starting point and the panel just appears. Second, transition-behavior: allow-discrete lets discrete properties like display participate in the transition instead of flipping instantly. Third — and this is the one almost everyone misses — you must include overlay in the transition property list, because overlay is what keeps the element in the top layer; without it the panel drops out of the top layer the moment closing starts and the exit animation plays behind everything else on the page. One ordering trap: the @starting-style rule has to come after the open-state rule it refers to, or it loses the cascade and does nothing.
How do I stop a popover from being cut off at the edge of the screen?
position-try-fallbacks. Declare the alternatives you will accept — flip-block, flip-inline, or both — and the browser tries the preferred position-area first, then each fallback in order until one fits the viewport. For finer control, write a named @position-try block with its own placement and margins and reference it by name. Two details make it work in practice. The fallback only fires when the panel would genuinely overflow, so test it with the anchor pushed close to an edge rather than in the middle of a comfortable layout. And anything visually tied to the anchor — a tooltip arrow especially — has to be positioned against the anchor rather than the panel, or it stays pointing the original direction after the panel flips and ends up detached, which looks worse than no arrow at all.
Should I use popover or the dialog element?
The question is whether the user is allowed to ignore it. Use <dialog> with showModal() when they must deal with it first: focus is trapped inside, the rest of the page goes inert, and the backdrop blocks interaction. That is right for a destructive confirmation, a required form, a blocking flow. Use popover when they can look at it and carry on: menus, tooltips, flyouts, filter panels, notifications. Focus is not trapped, the page stays interactive, and clicking elsewhere dismisses it. The common error is reaching for a modal because it is the pattern you know, and trapping a user inside a dropdown they only wanted to glance at. The features do overlap — a <dialog> can carry a popover attribute for non-modal use — but treat modality as the deciding question and the choice is usually obvious.
Is the Popover API accessible by default?
Partly, and knowing the gap is the point. The browser gives you keyboard dismissal with Escape, focus returning to the trigger on close, and correct top-layer rendering. It does not give the panel a role, an accessible name, or any relationship to its trigger — a popover is a generic element until you say otherwise. So you own three things. Put aria-expanded on the trigger and keep it in sync, because nothing does that for you and a screen reader user otherwise has no idea whether the panel is open. Give the panel the role its content deserves: menu with menuitem children for a menu, listbox with option children for a select. And if the panel is a hint describing the trigger, wire aria-describedby rather than using role="tooltip" on something click-triggered, which contradicts what a tooltip is. Keyboard navigation inside the panel — arrow keys, typeahead, roving tabindex — is still yours to implement.
How do these port to React, Vue, Svelte and Tailwind?
Better than most CSS, because the API is declarative and lives in markup rather than state. The seventeen pure-CSS demos need no JavaScript in any framework: popover and popovertarget are plain attributes, so they pass straight through JSX and templates. React needs one accommodation — attribute casing. Write popover="auto" and popoverTarget in JSX, and note that React versions before 19 did not pass unknown attributes through cleanly, so on older versions you may need ref plus setAttribute. Where you do call the imperative methods, guard them: showPopover() and hidePopover() throw if the element is not in the document or not currently in the required state, so wrap them in try/catch or check matches(':popover-open') first. In Next or Nuxt, feature detection must run in an effect rather than the component body, since HTMLElement does not exist during the server pass. For Tailwind the panel is a short utility chain plus the open: and starting: variants.

Related collections

30 CSS Badges preview

30 CSS Badges

30 hand-coded CSS badges for status indicators, notifications, membership tiers, live-data displays, and SEO / DevOps / financial dashboards — upload progress, typing indicator, transit line status, Core Web Vitals, ECG heartbeat, CI/CD build pipeline, countdown ring, live price ticker, keycap shortcut, wax seal, conference lanyard, and holographic collectibles. Copy-paste HTML and CSS, WCAG 2.2 accessible, MIT licensed.

20 CSS Banners & Alerts preview

20 CSS Banners & Alerts

20 hand-coded CSS banner and alert bars for e-commerce storefronts, SaaS dashboards, marketing sites, and compliance-bound products. Covers GDPR cookie consent with a real preferences panel, promo bars with countdown timers, free-shipping threshold progress, app-install smart banners, maintenance and incident status bars, newsletter signup bars, age verification gates, limited-stock urgency banners, live event announcements, browser upgrade notices, geo and currency switchers, sticky top announcement bars, bottom cookie bars, inline form validation alerts, icon-aligned alert banners, left-border accent alerts, diagonal stripe promos, full-width hero banners, animated gradient border alerts, and a text-wrapping laboratory that shows five strategies for the unbreakable string that breaks more banners than anything else. Every bar is scoped under a .ba-NN prefix for no-collision pasting, guards prefers-reduced-motion, carries the correct aria-live and role semantics, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.

25 CSS Blockquotes preview

25 CSS Blockquotes

25 CSS blockquotes for article pull quotes, testimonial cards, docs callout admonitions, statement banners, and social-post embeds. Covers giant decorative quotation marks, minimalist hairline and classic left borders, animated gradient and glowing neon borders, glassmorphism and aurora backgrounds, brutalist cards, speech bubbles, thick accent rails, highlighted mark text, newspaper multi-column layouts, an expandable read-more disclosure, a scroll-snap testimonial slider, and one-click copy.

Search CodeFronts

Loading…