20 CSS Popovers

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.

17 pure CSS3 light JSPublished

Related21 CSS Tooltips22 CSS Dropdown Menu Designs36 CSS Modals20 CSS Custom Select Dropdowns

Basic Popover Show and Hide — preview
01 / 20Pure CSS

Basic Popover Show and Hide

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.

Published

Auto vs Manual Popover Behavior — preview
02 / 20Pure CSS

Auto vs Manual Popover Behavior

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.

Published

Basic Anchored Popover — preview
03 / 20Pure CSS

Basic Anchored Popover

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.

Published

Discrete Entry and Exit Animations — preview
04 / 20Pure CSS

Discrete Entry and Exit Animations

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.

Published

Custom Backdrop and Light Dismiss — preview
05 / 20Pure CSS

Custom Backdrop and Light Dismiss

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.

Published

Grid Placement with Position Area — preview
06 / 20Pure CSS

Grid Placement with Position Area

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.

Published

Flip and Fallback Positions — preview
07 / 20Pure CSS

Flip and Fallback Positions

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.

Published

Tooltip Arrow Pointing to Anchor — preview
08 / 20Pure CSS

Tooltip Arrow Pointing to Anchor

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.

Published

Match Anchor Width for Dropdowns — preview
09 / 20Pure CSS

Match Anchor Width for Dropdowns

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.

Published

Submenus and Nested Popovers — preview
10 / 20Pure CSS

Submenus and Nested Popovers

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.

Published

Custom Select Listbox Popover — preview
11 / 20Light JS

Custom Select Listbox Popover

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.

Published

Tooltip on Hover and Focus — preview
12 / 20Light JS

Tooltip on Hover and Focus

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.

Published

User Profile Flyout Menu — preview
13 / 20Pure CSS

User Profile Flyout Menu

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.

Published

Date Picker Popover Panel — preview
14 / 20Pure CSS

Date Picker Popover Panel

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.

Published

Popover Menu with Checkbox Filters — preview
15 / 20Pure CSS

Popover Menu with Checkbox Filters

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.

Published

Cross Browser Fallback and Polyfill — preview
16 / 20Light JS

Cross Browser Fallback and Polyfill

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.

Published

Popover vs Dialog Showcase — preview
17 / 20Pure CSS

Popover vs Dialog Showcase

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.

Published

Glassmorphic Popover Overlay — preview
18 / 20Pure CSS

Glassmorphic Popover Overlay

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.

Published

Dark and Light Mode Popover — preview
19 / 20Pure CSS

Dark and Light Mode Popover

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.

Published

Accessible Tooltip with ARIA Roles — preview
20 / 20Pure CSS

Accessible Tooltip with ARIA Roles

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.

Published

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 hand-coded CSS blockquote designs for SaaS testimonials, editorial pull quotes, documentation callouts, and developer engineering blogs: large decorative quotation marks with ::before / ::after glyphs, Tailwind CSS blockquote component, pure CSS callout / admonition boxes for Docusaurus / Mintlify / Nextra documentation, responsive center-aligned quote banners, modern minimalist left-border editorial style, animated CSS gradient border, classic left border, brutalist high-contrast, glassmorphism frosted card, glowing neon border, article pull quote with text-wrap, speech bubble chat-style, thick vertical accent rail, customer testimonial cards with avatar and star rating, inline highlighted text, marker highlight underline, floating drop-shadow, aurora gradient background, Twitter / X card style, multi-column newspaper editorial, IDE code-comment style, Markdown-compatible defaults, JavaScript testimonial quote slider, expandable read-more, and one-click copy button. 22 Pure CSS + 3 Light JS (slider, expand, copy). Every design scoped under .bq-NN prefix for no-collision pasting, prefers-reduced-motion guarded per WCAG 2.3.3, WCAG 2.2 AA accessible, framework-agnostic (React, Vue, Svelte, Astro, Next.js, Nuxt, Remix, SvelteKit).

Search CodeFronts

Loading…