webdevpuneet.com

Tippy.js Interactive Popover with Form — Free HTML CSS JS Snippet

Tippy.js Interactive Popover with Form · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="pf-wrap">
  <button class="pf-trigger" id="pfTrigger" type="button">Assign to &#9662;</button>
  <div id="pfContent" style="display:none">
    <div class="pf-pop">
      <div class="pf-pop-title">Assign task to</div>
      <input type="text" class="pf-search" id="pfSearch" placeholder="Search people...">
      <ul class="pf-people" id="pfPeople"></ul>
    </div>
  </div>
  <div class="pf-result" id="pfResult">Assigned to: <b>Nobody</b></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Genuinely usable popover content
interactive: true keeps it open while you interact with it.
Real DOM element as content
Existing event listeners keep working once moved into the popover.
Fresh state on every open
onShow clears stale search text and re-renders the full list.
Correctly deferred autofocus
Avoids focusing an element before it's actually mounted.
Programmatic close on selection
Choosing an option completes the interaction explicitly.
Custom light popover theme
Plain CSS on Tippy's own theming attribute.

About this UI Snippet

Tippy.js Interactive Popover with Form — the One Option That Turns a Tooltip Into a UI

Screenshot of the Tippy.js Interactive Popover with Form snippet rendered live

A tooltip you can only read and a popover you can actually interact with — type into, click things inside — are built on the same library, but they need one specific, easy-to-miss option set correctly. Get it wrong and the popover closes itself the instant you try to use it.

interactive: true is what keeps the popover open while you use it

By default, Tippy treats its content as decoration: moving the mouse away from the trigger element starts the hide timer immediately, even if the cursor is heading straight for the tooltip's own content. interactive: true changes that — Tippy now also considers the popover's own content area as "still hovering," so a search input and clickable list inside it can actually be used without the whole thing vanishing mid-interaction.

content is a real DOM element, not a string

Rather than passing a content string, content: pfContent hands Tippy a genuine, pre-built DOM node containing a real <input> and <ul>. Tippy moves that exact node into its popover rather than cloning or re-parsing it, which is what makes the search input's event listeners (already attached before Tippy ever sees the element) keep working once it's inside the popover.

The template's inline display:none has to be cleared first

The content markup starts with style="display:none" so it doesn't flash visibly in the page before Tippy adopts it — but that inline style travels *with* the element into the tippy-box, and Tippy's show/hide only toggles the box wrapper, never an inner element's own pre-existing style. pfContent.style.display = '', run once right after capturing the reference and before tippy() is called, is what prevents the popover from reporting itself as fully shown while its actual content stays invisible underneath.

onShow resets and refocuses on every open, not just the first

Each time the popover opens, onShow clears the previous search text and re-renders the full people list — so re-opening after a previous search doesn't show stale filtered results. Focusing the search input is deferred with setTimeout(..., 0): at the exact moment onShow fires, Tippy is still in the process of mounting the popover's content into the DOM, so an immediate .focus() call would target an element not yet attached to the page and silently fail.

Selecting a person closes the popover programmatically

Clicking a person calls instance.hide() directly — the same Tippy instance the whole popover was created from — rather than waiting for an outside click or hover-out to close it. This is what makes selection feel like a completed action instead of leaving an already-answered popover sitting open.

trigger: 'click' plus interactive: true is the standard popover recipe

Combining a click-based trigger (rather than hover) with interactive: true is specifically the pattern for "click to open a small interactive panel" — hover-triggered interactive popovers exist but are unusual UX, since a hover-opened panel with clickable content inside it is easy to accidentally dismiss.

Reusing it

Swap the people list for any searchable option set — tags, projects, reviewers, categories — the interactive: true plus real-DOM-element pattern is what generalizes; only the rendered content and selection handler change.

Step by step

How to Use

  1. 1
    Add the Tippy.js CDNLoad tippy.css and tippy-bundle.umd.min.js before the snippet's JS runs.
  2. 2
    Paste HTML, CSS, and JSA single "Assign to" button renders.
  3. 3
    Click the buttonA popover opens with a search box, auto-focused.
  4. 4
    Type a nameThe list filters live as you type.
  5. 5
    Move your mouse into the popoverIt stays open — this is what interactive: true enables.
  6. 6
    Click a personThe popover closes and the assignment updates below.

Real-world uses

Common Use Cases

Task assignee pickers
Build the exact pattern for choosing who owns an issue, with a live search input and clickable list that stay open because interactive: true is set.
Tag and label selectors
Provide searchable multi-option pickers inside a popover, using an existing DOM element as content so event listeners keep working.
Quick filter dropdowns
Pair with the Tippy dropdown menu with submenus for a consistent family of popovers in quick-filter dropdowns.
Reviewer and approver selection
Offer compact people pickers inside an inline card, with onShow clearing stale search text and re-rendering the full list each time.
Contextual quick actions
Place any small interactive form inside a popover, deferring autofocus until the element is mounted so the cursor lands correctly.

Got questions?

Frequently Asked Questions

By default, Tippy starts hiding its content as soon as the mouse leaves the trigger element, regardless of where the cursor is heading next. Without interactive: true, moving the mouse from the trigger button toward the popover's own content counts as "leaving," so the popover closes before the cursor ever reaches the search input or list — interactive: true tells Tippy to also treat hovering the popover's own content as still active, which is what lets it be used at all.

The search input and list already have their own event listeners attached in JavaScript before Tippy is ever initialized. Passing the actual DOM element (via document.getElementById) means Tippy moves that exact, already-wired-up element into its popover, preserving those listeners. Passing an HTML string instead would have Tippy parse fresh, listener-less DOM from that string, requiring listeners to be re-attached after every popover open.

At the exact moment the onShow callback fires, Tippy is still in the process of mounting the popover's content into the live DOM — calling .focus() on the search input synchronously at that point can target an element that isn't fully attached yet and silently do nothing. Deferring the focus call with setTimeout(fn, 0) pushes it to run just after the current mounting work finishes, by which point the input is reliably present and focusable.

Calling instance.hide() directly on the Tippy instance closes the popover the moment a selection is made, which reads as a completed action — the equivalent of a dropdown closing itself after you pick an option. Leaving it open and waiting for an outside click would leave an already-answered popover sitting on screen, requiring an extra, unnecessary action from the user to dismiss it.

Replace the PEOPLE array and renderPeople function with whatever searchable option set you need (tags, categories, projects), keeping the same shape: a search input filtering a list, and a click handler on each option that updates your result state and calls instance.hide(). The interactive: true configuration, the real-DOM-element content, and the onShow reset logic all carry over unchanged.