Popover — Free HTML CSS JS Snippet, 3 Positions

Popover · Modals · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

3 positions: .bottom (top:100%+10px), .top (bottom:100%+10px), .right (left:100%+10px)
Directional arrow: 8px div, two-sided border, rotate(45deg), per-direction CSS
Scale+opacity animation: scale(0.96)→1 + opacity 0→1, transform-origin matches position
Click-outside: document click + e.target.closest(".pop-wrap") check
ESC close: document keydown listener closes all open popovers
Action menu pattern: Edit, Duplicate, divider, Delete (danger class, red hover)
Info popover: dashed trigger border, paragraph body with inline link
Shortcuts panel: kbd elements with border, monospace font, right-aligned labels

About this UI Snippet

Popover — Action Menu, Info Panel & Keyboard Shortcuts with 3 Positions, Arrow & Click-Outside

Screenshot of the Popover snippet rendered live

A popover is a floating panel that appears adjacent to a trigger element when clicked, showing contextual actions, information, or secondary content without navigating away. It is more persistent than a tooltip (which shows on hover) and less disruptive than a modal. This snippet provides three popover variants in three positions: an action menu with divider and danger item (below), an information panel with a link (above), and a keyboard shortcuts list (right) — all with CSS animation, directional arrows, click-outside close, and ESC support.

The position system

Three CSS classes — .bottom, .top, and .right — position the popover relative to its trigger. .bottom uses top: calc(100% + 10px) and left: 0. .top uses bottom: calc(100% + 10px). .right uses left: calc(100% + 10px). The 10px gap leaves room for the arrow. transform-origin is set per direction so the scale animation originates from the correct edge of the popover.

The directional arrow

Each position variant has a matching .pop-arrow CSS rule that positions and rotates an 8×8px div. The div has background: #fff and border on two sides — the sides that form the outward-pointing corner. For .bottom, the arrow is positioned at top: -5px with border-bottom and border-right removed so only the top-left corner is visible. Rotating 45 degrees makes it point upward toward the trigger.

The open/close animation

Popovers use opacity: 0 + pointer-events: none + transform: scale(0.96) as the closed state. Adding .open switches to opacity: 1 + pointer-events: all + transform: scale(1). The CSS transition: opacity 0.15s, transform 0.15s animates both simultaneously. The transform-origin direction-matching ensures the popover scales outward from the trigger edge.

Click-outside and ESC close

A document click listener checks e.target.closest(".pop-wrap") — if the click was outside any .pop-wrap container, all open popovers close. ESC calls close() for all open popovers. Both listeners are attached once to the document and work for any number of popovers on the page.

The action menu pattern

The action popover demonstrates the standard three-item action menu pattern: Edit, Duplicate (utility actions in default colour), divider, Delete (destructive action in red with danger class). This exact pattern is used in Notion, Linear, GitHub, and most SaaS interfaces for row and card actions.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out the arrow geometry or the click-outside detection by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how removing two of the four borders on the small rotated square in pop-arrow produces a directional triangle-like corner for each of the three position variants, and how the openPopovers Set combined with the document-level click listener's closest check ensures exactly one popover stays open at a time. The same assistant can help optimize it, for example checking whether the current close function's querySelectorAll over every trigger is more work than necessary versus tracking the currently-open trigger directly. It's also useful for extending the effect: ask it to add a fourth "left" position variant following the same arrow pattern, make the popover reposition itself automatically when it would overflow the viewport edge, or add a nested submenu popover that opens from within another popover. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a floating popover component in plain HTML, CSS, and vanilla JavaScript that supports at least three directional positions (below, above, and to the right of its trigger) — no libraries, no positioning library like Popper.

Requirements:
- Each popover lives inside a position: relative wrapper alongside its trigger button, and the popover panel itself uses position: absolute with one of three modifier classes controlling whether it appears below (top: 100% plus a gap), above (bottom: 100% plus a gap), or to the right (left: 100% plus a gap) of the trigger, with a matching transform-origin for each direction so a scale-in animation grows from the correct corner.
- A small square "arrow" element for each popover that is rotated 45 degrees and has exactly two of its four borders removed (matching the direction it needs to point) so it reads as a directional triangle pointing back at the trigger, positioned and colored to blend seamlessly with the popover panel's edge.
- Clicking a trigger toggles its associated popover open or closed using an opacity/transform-based CSS transition (not display toggling) so the open state can animate in and out smoothly, and clicking any other trigger while one popover is open must close the first one before opening the new one — only one popover open at a time.
- A single document-level click listener that closes every currently open popover whenever the click target is not inside any popover-and-trigger wrapper (checked via the closest DOM method), and a single document-level keydown listener that closes every open popover when the Escape key is pressed.
- Populate the popovers with at least three different content patterns: an action menu with a divider and a distinct "danger" styled destructive action, an informational panel with a paragraph and a link, and a list of keyboard shortcuts using kbd elements.

Step by step

How to Use

  1. 1
    Click any trigger to open the popoverClick the trigger button to open the attached popover. Click the same button or anywhere outside the popover to close it. Press ESC to close all open popovers. Only one popover can be open at a time.
  2. 2
    Choose the position classApply .bottom, .top, or .right to the .popover div depending on where the popover should appear relative to the trigger. The arrow direction and transform-origin update automatically from the position class.
  3. 3
    Add your action itemsDuplicate .pop-item buttons inside .pop-body for each action. Add class="danger" for destructive actions (red text, red hover background). Add a .pop-divider div between groups of related actions.
  4. 4
    Add click-outside detection to new popoversThe document click listener automatically closes any popover not inside a .pop-wrap. New popovers work without additional event listener code — just give them a unique id and matching trigger button.
  5. 5
    Use for tooltips with rich contentReplace .pop-body with .pop-info for a paragraph-style info popover. Add links, code snippets, or images inside .pop-info. The popover expands to fit content — set max-width: 280px to constrain very long text.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component using useState for open state and useEffect for click-outside detection, or "Tailwind" for a Tailwind CSS version.

Real-world uses

Common Use Cases

Three-dot action menus for table rows and card items
The action menu variant (Edit, Duplicate, Delete) is the standard popover pattern for contextual actions on data rows, file items, project cards, and any list where each item has multiple actions — closely related to the dropdown menu. The divider separates utility from destructive actions.
Contextual help and documentation popovers
The info popover variant (dashed trigger, paragraph body, link) is ideal for inline help text next to form fields, table column headers, dashboard metrics, and feature labels. It answers "what is this?" without navigating to a separate docs page.
Keyboard shortcut references and feature discovery
The right-side shortcuts panel shows keyboard shortcuts adjacent to the relevant UI element. Placing shortcut hints near the feature they apply to teaches users the keyboard interface in context — higher retention than a separate help page.
Share, export, and link copy action panels
Use a popover for share options: "Copy link", "Share to Twitter", "Download as PDF". The popover stays open while the user selects their share method, then closes on action. Wire the copy link button to the Clipboard API for immediate feedback.
Filter and sort option panels in listing UIs
Trigger a popover from a "Filter" or "Sort" button to show filtering options for a list or table. The popover stays open while the user adjusts multiple filters, then closes when they click Apply or click outside.
User profile mini-cards and hover detail panels
Show a user profile mini-card popover when clicking a mention or avatar: avatar, name, role, contact button. This pattern is used in Slack, Linear, and GitHub to show user context without navigating to the profile page.

Got questions?

Frequently Asked Questions

The arrow is an 8×8px div with background: #fff and border on all four sides initially. For .bottom, the top two borders (top and left) are visible by removing border-bottom and border-right, then rotating 45deg — this creates a corner pointing upward. For .top, the bottom two borders are removed. For .right, the right and top borders are removed. The arrow is absolutely positioned at the edge closest to the trigger: top: -5px for .bottom, bottom: -5px for .top, left: -5px for .right. The -5px is half the 8px height/width, centering the arrow on the popover edge.

A click listener on document fires for every click. It checks e.target.closest(".pop-wrap") — the closest() method walks up the DOM tree from the clicked element to find a .pop-wrap ancestor. If no .pop-wrap is found (the click was outside all popovers and their triggers), closest() returns null and all open popovers close. If a .pop-wrap is found, the click was inside a popover or on a trigger, so existing popovers stay open. The toggle() function handles opening the correct popover for each trigger click.

Add a .left position variant: .popover.left { right: calc(100% + 10px); top: 0; transform-origin: top right; }. Arrow CSS: .popover.left .pop-arrow { right: -5px; top: 14px; transform: rotate(45deg); border-left: none; border-bottom: none; }. Use class="popover left" on the popover div and add the left trigger button with onclick="toggle('pId','tId')".

Click "JSX" to download. Manage openId state with useState<string|null>(null). Toggle: setOpenId(prev => prev === id ? null : id). Apply open class conditionally: className={"popover bottom" + (openId === "p1" ? " open" : "")}. For click-outside, use useEffect to add a document click listener and check if e.target.closest(".pop-wrap") is null — if so, setOpenId(null). Return a cleanup from the useEffect: return () => document.removeEventListener("click", handler).