
Basic Popover Show and Hide
Published
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 Dropdowns

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published
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.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.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.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.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.<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.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.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.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 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 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).