This page features interactive demos and side-by-side code samples that need at least 340 pixels of horizontal space. Please widen your browser window, or rotate your device, to continue.
Component <neo-popover> is an anchored floating panel attached to a trigger, with a [data-neo-popover-trigger] child and a [data-neo-popover-content] panel. The host owns positioning, the open/close events, outside-click and Esc dismissal, and the strict-placement clamp logic.
Default
Open
Placements
With actions
Tall content (clamps + scrolls)
Strict placement
Wide content (horizontal clamp)
Hover to open
DeactivateRenameDuplicateDelete
The preview routes through the in-browser server simulator. Its current Settings (network latency, a non-200 response, or an unreachable server) are in effect, so the live preview can look like it is misbehaving. Adjust them in the Settings panel.
New state Toggle autoplay
Open popover
Hello
This is a basic popover. Press Esc or click outside to close.
HTMLCSS
The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.
Signals
Datastar signals declared in the selected state's markup. Change a value to drive the preview, applied live while Sync is on, or when you click Patch.
What clicking the trigger does. Default toggles open and closed; show only opens (for hover-then-click patterns like submenus, where a click must not close an already-open panel); hide only closes.
Margin kept clear of the viewport edges. Mirrors to --neo-popover-screen-offset.
follow-scroll
"always" | "until-trigger-invisible"
unset
When absent, close on outside scroll. Set to always to stay anchored to the trigger during scroll. Set to until-trigger-invisible to stay anchored until the trigger leaves the viewport, then close.
clamp-placement
boolean observed
false
Opt out of the default strict placement. The panel position re-anchors to keep itself inside the viewport when content would otherwise overflow, instead of staying glued to the configured side and shrinking max-width/max-height.
min-fit-height
CSS length | "content" observed
"content"
Minimum vertical slot the strict placement requires. If the slot at the configured side is shorter and the opposite side has more room, the panel auto-flips (e.g. bottom-start -> top-start). content resolves from the panel's scroll height on each positioning pass. Mirrors to --neo-popover-min-fit-height.
min-fit-width
CSS length | "content" observed
"content"
Same as min-fit-height but for the horizontal axis (left-*/right-*). content resolves from the panel's scroll width. Use 0 to disable the horizontal flip. Mirrors to --neo-popover-min-fit-width.
min-open-height
CSS length observed
2rem
Minimum usable vertical slot required to remain open after placement and auto-flip. If the bounded slot is smaller, the popover closes instead of rendering a cramped panel. Use 0 to disable. Mirrors to --neo-popover-min-open-height.
min-open-width
CSS length observed
2rem
Same as min-open-height but for horizontal space. Use 0 to disable. Mirrors to --neo-popover-min-open-width.
flip
true | false observed
true
Auto-flip to the opposite side when the min-fit-* threshold is not met. Set flip="false" to pin the panel to the configured primary side; if opening would then make it too cramped, the popover scrolls the trigger into view and opens immediately.
match-trigger-width
boolean observed
false
Size the panel's border-box width to the trigger's border-box width on every positioning pass. Used by neo-select and neo-combobox.
hover
boolean
false
Open on pointer hover over the trigger; close after the pointer leaves both the trigger and the content. Mouse only; touch falls back to click. While set, clicking the trigger always shows the panel (instantly, no delay) so touch and keyboard users have a way in; click never toggles it shut.
hover-open-delay
integer (ms)
100
Milliseconds the pointer must rest on the trigger before hover opens the panel. Filters out incidental cursor crossings. Does not apply to click.
hover-close-delay
integer (ms)
200
Milliseconds to wait after the pointer leaves both trigger and content before hover closes the panel. Long enough to bridge the gap between trigger and panel without snapping shut.
Twelve combinations: each side (top, right, bottom, left) paired with optional -start or -end alignment. Bare side names center the panel on that edge; bottom-start is the initial value.
Pair the popover with a confirm / cancel toolbar inside the content panel. The action buttons carry data-neo-popover-close so picking either dismisses the panel.
When the panel is taller than the viewport it clamps to the screen (leaving screen-offset of margin) and scrolls internally rather than overflowing the page.
Tall content (clamps + scrolls)
Long list of countries
Panel is taller than the viewport. It clamps to the screen (leaving screen-offset of margin) and scrolls internally.
Setting flip="false" pins the panel to bottom-start and keeps that side instead of flipping above when the slot runs short. When the bottom slot is shorter than min-fit-height, opening the popover scrolls the trigger into a useful position and shows the panel immediately. Set the clamp-placement attribute to let the panel re-anchor inside the viewport instead. Scroll so the trigger sits near the bottom of the viewport, then open the panel to see it scroll into place.
Strict placement keeps the panel glued to the configured side. When that slot is shorter than min-fit-height (here 8rem; default content) and the opposite side has more room, the primary axis flips: bottom-start becomes top-start. Scroll so the trigger sits near the bottom of the viewport, then open the panel to see it flip.
When the panel is wider than the viewport it clamps to the screen (leaving screen-offset of margin) and scrolls horizontally inside the panel. The horizontal clamp mirrors the vertical one.
Wide content (horizontal clamp)
Browse
This panel is wider than the viewport. It clamps to the screen (leaving screen-offset of margin) and scrolls horizontally.
Set hover on the host and the panel opens when the pointer enters the trigger, closing after the pointer leaves both the trigger and the panel. The hover-open-delay filters incidental cursor crossings; hover-close-delay gives the pointer time to bridge the gap from trigger to panel before the close kicks in. Click still works (instantly, no delay) so touch and keyboard users have a way in; clicking a hover popover never toggles it shut.
Hover or click me
Hover-opened
Move the pointer away to dismiss. The panel waits hover-close-delay ms first so a short trip through the gap doesn't close it.
A Datastar app can fat-morph the page at any moment, so a <neo-popover>'s panel content can change while it is open. Open the popover and watch autoplay push progress into it: the panel stays open and its content reconciles in place because the host element stays mounted across the morph, with no open attribute needed.
Initial
Progress: 50%
Progress: 75%
Progress: 100%
DeactivateRenameDuplicateDelete
The preview routes through the in-browser server simulator. Its current Settings (network latency, a non-200 response, or an unreachable server) are in effect, so the live preview can look like it is misbehaving. Adjust them in the Settings panel.
New state Toggle autoplay
Show upload
Preparing…
HTMLCSS
The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.
Signals
Datastar signals declared in the selected state's markup. Change a value to drive the preview, applied live while Sync is on, or when you click Patch.
Popovers nest naturally. Each child popover lives inside its parent's content panel, so a click inside any open level keeps every ancestor open and a click outside closes the whole chain. Esc closes one level at a time. Submenu triggers are regular menu items; non-submenu items carry data-neo-popover-close so picking one collapses the entire stack. Hovering a submenu trigger opens it; hovering a sibling closes any open submenu at that level.
A popover that fetches its body on open, shows a loading placeholder while the request is in flight, and swaps in a failure placeholder once Datastar exhausts its retry budget (it dispatches datastar-fetch with type: 'retries-failed'). On success the server morphs the body in place.
The demo retries with retry: auto, which covers only network throws. Use the server unreachable control to force one: Datastar exhausts the retry budget, fires retries-failed, and the failure placeholder swaps in. A server response status error is not retried by auto, so the swap falls back to finished-after-error. Also see Datastar action options.
Recent activity
Diego Vega
CFO
Jan O'Sullivan
Lead Designer
Evelyn Kone
Data Analyst
Samira Khalil
Head of Engineering
Loading states only show with simulated delay. Raise network latency or handler delay in the Settings panel.