Pricing

Popover handbook

v3.1.1 · @snowui-design-system/components

License: Before reading component documentation, source, or npm usage instructions for the first time, read the full LICENSE. Reuse that review for the same task and version. Public API configuration and internal implementation changes have different license implications. All components use the unified runtime package; source rights remain governed by the license.

Overview

Show contextual content in a floating panel anchored to an independent trigger, with click or hover activation and viewport-aware placement.

When to use it

It is used for floating content related to trigger anchor points. Tooltip is used for short text prompts, and Dialog is used for the focus process of the dialog box; open closeOn is used for closing behavior.

  • As="popover" can automatically associate anchors in the trigger host; static mode can be manually controlled by bind:open.

  • Trigger controls the trigger method, closeOn controls the shutdown policy; the interaction example that needs to be continuously displayed can be set to closeOn="manual".

Quick start

This example shows the recommended basic composition. See the sections below for more cases and composition limits.

<script lang="ts">	import { Button, Kbd, MenuItem, Popover, SearchInput, Text } from '@snowui-design-system/components';	const actions = [		{ value: 'copy-import', label: 'Copy import', shortcut: '⌘ C' },		{ value: 'open-figma', label: 'Open in Figma', shortcut: '' },		{ value: 'view-source', label: 'View source', shortcut: '' }	];	let query = $state('');	const filteredActions = $derived(actions.filter((action) => action.label.toLowerCase().includes(query.toLowerCase())));</script> <Popover size="md" closeOn="manual">	<SearchInput as="search" bind:value={query} placeholder="Search…" aria-label="Search component actions" />	{#each filteredActions as action}		<MenuItem as="item" value={action.value}>{action.label}{#if action.shortcut}<Kbd as="trailing">{action.shortcut}</Kbd>{/if}</MenuItem>	{:else}		<Text style="padding: var(--padding-3); color: var(--black-40);">No matching actions</Text>	{/each}</Popover>

API

Keyword Type Default Description
id string — The unique identification of the template mode
template boolean false Template mode; do not render directly, activate through showPopover(id)
size PopoverSize "sm" Size gear
trigger PopoverTrigger "click" Trigger mode; template mode takes effect
placement PopoverPlacement "bottom" Give priority to the pop-up direction, and automatically flip when the viewport space is insufficient.
offset number 8 The distance between the floating layer and the trigger, unit px
closeOn PopoverCloseMode "outside" Closing method: outside Click blank to close, manual only close by the Popover button
closeOnEscape boolean true Whether Escape is turned off; both closeOn modes are supported by default
closeOnSelect boolean — Click whether the internal MenuItem is closed; a single MenuItem closeOnSelect can override the default behavior: true (sm) / false (md/lg).
continuousHover boolean true Whether to use container-level continuous hover background between MenuItem directly
open boolean true Static mode manual control display, support bind:open
onOpenChange (open: boolean) => void — Turn on the state change callback
as "popover" | undefined — as="popover" Enable embedded mode: Automatically find that the host parent element is anchor and trigger source
children Snippet — Panel content

See the base component documentation for inherited capabilities not redeclared here. This table lists only named public Props declared by this component.

Usage boundaries and details

Three modes (Modes)

Popover supports three usage modes at the same time: Panel mode (Panel), Inline mode (Inline), Template mode (Template).

1. Panel Mode (Panel Mode) - Default

Without as or template, Popover is directly rendered as a panel. It is suitable for free combination of search floating layers, menu floating layers and other scenarios (SP-02 style).

<Popover size="md" style="width: 30rem;">    <SearchInput placeholder="Search" />    <MenuItem>Result A</MenuItem></Popover>

2. Inline Mode - Recommended for "button trigger" scenarios

After marking as="popover", put it into any host element (Button / Frame / Icon, etc.), and Popover will automatically:

  • Find the parent element as anchor

  • Bind the parent element event by trigger="click|hover"

  • Portal renders floating layer to body, with 8px offset and viewport avoidance

No need for id, no need for data-popover-target, no need for handwritten event callback. Refer to the writing method of Tooltip A1.

<Button>    More    <Popover as="popover">        <MenuItem>Notifications</MenuItem>        <MenuItem>Favorites</MenuItem>    </Popover></Button>  <Frame row class="p-8 bg-black-4">    Click me    <Popover as="popover" trigger="hover" size="md">        <Text>Hover details</Text>    </Popover></Frame>

3. Template Mode - for cross-component activation / multi-trigger sharing

Register by template + id, and then activate with showPopover(id, anchorEl) or the declarative attribute data-popover-target="id". The trigger button is independent of Popover and associated by ID (refer to Toast TS-10).

<Popover template id="pop-01">    <Text>Popover Content</Text></Popover>  <Button data-popover-target="pop-01">Trigger 1</Button><Button data-popover-target="pop-01">Trigger 2</Button>  <Button onclick={(e) => showPopover("pop-01", e.currentTarget)}>Click Me</Button>

Quick check of mode selection

Scene Recommended mode
One button + one floating layer (95% use cases) Embedded as="popover"
Multiple triggers share a piece of content / command activation / cross-component Template template + id
Not the floating layer, directly as the panel combination content (such as SP-02) Panel (default)

Layout model

Main layout model: composite management layout.

  • Template mode: only carries registration and Portal floating rendering; triggers are provided independently by the caller.

  • Static mode: Popover is rendered by the panel itself.

  • The panel content defaults to column vertical arrangement, gap: 0, and the content is freely combined by the user (MenuItem, Text, Frame, etc.).

  • Floating layer position, level, visible and hidden, and viewport collision avoidance belong to the component's private management logic.

Floating structure (fixed head + independent rolling body)

Popover and Listbox share the same set of header + scroll-body structure, and all floating layers are consistent with scrolling behavior:

.snowui-popover .snowui-quick-action-panel├── .snowui-popover-header.snowui-quick-action-panel-header   ← Input/SearchInput moved automatically; does not scroll└── .snowui-popover-body.snowui-quick-action-panel-scroll-body    ├── .snowui-popover-scroll-content.snowui-overlay-scrollbar-viewport   ← user children render and scroll here    └── .snowui-popover-scrollbar.snowui-overlay-scrollbar                ← custom Thumb
  • Through LISTBOX_HEADER_PORTAL_KEY context, expose headerEl;<Input as="search"> and <SearchInput> (default as="search") are mounted and automatically appendChild to the header by Input's own $effect, without participating in the scrolling below.

  • Floating root SnowFrame explicit transmission enableAutoLayout={false}: The panel itself is responsible for display:flex; flex-direction:column; gap:0, and does not access the default gap-2 of [data-auto-layout], ensuring that the header→body spacing is completely controlled by header padding-bottom (4px).

  • When there is content in the header, it is provided by the shared base class .snowui-quick-action-panel-header:not(:empty) padding-bottom: var(--padding-1)(4px), as the visual spacing between the search box and the list; prohibits rewriting margin/gap in the component to copy the 4px.

  • Body flex: 1; min-height: 0, scroll viewport reuse .snowui-overlay-scrollbar-viewport(overflow:scroll + scrollbar-width:none); Thumb calculates the position through useOverlayScrollbar hook.

  • Header input box style: unified selector in Input :where(.snowui-quick-action-panel-header > .snowui-input, ...) Inject compact padding/gap/radius and close the outer ring in :focus-within (-snowui-field-focus-ring-width: 0rem); users do not need to overwrite externally.

  • The right edge of the floating layer scroll bar thumb is 4px away from the inner border of the panel (scroll bar specification). Because scrollbar is absolutely positioned in the body and the panel box-sizing: border-box, offset needs to offset the panel itself padding:sm padding=12 → right:-8px, md padding=16 → right:-12px, lg padding=24 → right:-20px. The three-speed size has been declared separately in the component, and it needs to be synchronized when adding size.



Measurement

size padding border-radius Typical uses
sm (default) 12px (--padding-3) 16px (--radius-4) Replace the old QuickAction; shortcut menu
md 16px (--padding-4) 24px (calc(--radius-3 * 2)) Replace the old SearchPopup; search floating layer, information card
lg 24px (--padding-6) 32px (calc(--radius-4 * 2)) Large-scale information floating layers and complex forms

Trigger (template mode)

  • Click (default): Click the trigger to switch Popover to show and hide

  • Hover: After hovering the trigger, the delay of 450ms appears; after moving out of the trigger and floating layer, 80ms is closed. The first hover delay is unified with Tooltip; Popover does not participate in Tooltip's 300ms quick switch window.

Shut

  • closeOn="outside"(default): Click Popover to close the outer area

  • closeOn="manual": Only call hidePopover(id) or bind:open=false by the button in Popover to close

  • Escape:closeOnEscape={true}(default) Any off mode can be closed by pressing Escape


Position

The default preferred direction is bottom, and the distance from the trigger is 8px. When placement is not configured or there is a visual port collision, select the visible direction in the order of prefer → opposite direction → vertical/horizontal candidate to ensure that the floating layer does not appear outside the visible area.


Command API

Defined in ../logic/popover.svelte.ts, and the full document can be found in ../logic/popover.md (source code reference: components/src/logic/popover.md).

import { showPopover, hidePopover, togglePopover } from "@snowui-design-system/components"; showPopover("pop-01", buttonEl);   // show (450ms delay in hover mode)hidePopover("pop-01");             // hide (80ms delay in hover mode)togglePopover("pop-01", buttonEl); // toggle

Template + click to trigger

<Popover template id="pop-menu">    <MenuItem>Notifications</MenuItem>    <MenuItem>Favorites</MenuItem>    <MenuItem>Share</MenuItem></Popover> <Button data-popover-target="pop-menu">More</Button>

Template + hover trigger + medium size

<Popover template id="pop-info" trigger="hover" size="md">    <Text>Detailed information</Text></Popover> <Button data-popover-target="pop-info">Hover to view</Button>

Template + manual shutdown

<Popover template id="pop-settings" closeOn="manual" size="lg">    <Text>Settings</Text>    <Button onclick={() => hidePopover("pop-settings")}>Done</Button></Popover> <Button data-popover-target="pop-settings">Open settings</Button>

Static mode (SP-02 style search floating combination)

<Popover size="md" style="width: 30rem;">    <SearchInput placeholder="Search" />    <Frame column class="gap-4">        <Text class="text-black-40">Recent</Text>        <MenuItem>Landing page</MenuItem>        <MenuItem>ByeWind</MenuItem>    </Frame></Popover>

Merger description

Old components New equivalent writing method
<QuickAction>...</QuickAction> <Popover size="sm">... </Popover>(static) or <Popover template id="..." size="sm">... </Popover>(Template)
<SearchPopup>...</SearchPopup> <Popover size="md">...</Popover>

The QuickAction and SearchPopup source code components have been deleted; the SP-02 sample is migrated to the component sample and kept for the search floating layer combination reference.

2026-10-07 The new block SearchPopup is based on the combination of search input and group results of Popover, and does not restore the old SearchPopup floating layer implementation; SP-02 now consumes the block.

1. Template host (template mode)

  • <Popover template id="X"> Do not render any DOM, only register the configuration and content to the store

  • Activate by showPopover(id, anchorEl) or data-popover-target="id"

  • The same id can only have one active instance at the same time; the subsequent show call replaces anchor

2. Floating panel .snowui-popover

  • Reuse .snowui-quick-action-panel

  • position: fixed,z-index: 1000

  • gap: 0, the content is freely combined by the user

  • padding / border-radius file by size

3. Trigger agreement

  • Triggers are arbitrary interactive elements (Button, Icon, Frame, etc.), and are no longer wrapped by Popover.

  • Associated by data-popover-target="id" or onclick={() => showPopover(id, e.currentTarget)}


Continuous Hover

continuousHover defaults to true, which acts on the direct MenuItem of the scrolling content area. The single background layer moves continuously between menu items; false restores the original item-by-item hover of MenuItem. Nested Listbox uses its own recent continuous hover container, and is not taken over by Popover.

Popover style overlay

Read the full LICENSE first; reuse that review for the same task, version, and scope.

First use public component Props to match the desired appearance, preserving the existing look where possible. If changes are necessary, follow the style selection and override order: use your own class (including Tailwind already in the project) or an instance style. The names below are public overrides actually consumed by source; check their scope and do not modify private DOM. Choose the correct SnowUI token family. External apps need not install SnowUI website utilities.

Variable Purpose and default reference
--snowui-menu-item-radius-override Border-radius (default var(--radius-3) = 12px)
--snowui-popover-font-size-override Font-size(default var(--text-14) = 14px)
--snowui-popover-line-height-override Line-height(default var(--leading-14))
--snowui-popover-padding-override Padding (default sm 12 / md 16 / lg 24)
--snowui-popover-radius-override Border-radius (default sm 16 / md 24 / lg 32)

Design Guide · Token · Combination boundary

Popover type and status

Required Props: no named required Props.

Two-way binding: bind:open.

Inheritance: Omit<FrameProps, "children">. Consult the base class public documentation; fields excluded by Omit cannot be passed through unchanged.

Share Frame API · Return API

The following type declarations are actually referenced by public Props and come from source. A Svelte Snippet denotes a content fragment; ComponentProps denotes the Props of the corresponding component.

PopoverSize

Source: components/src/logic/popover.svelte.ts

export type PopoverSize = "sm" | "md" | "lg";

PopoverTrigger

Source: components/src/logic/popover.svelte.ts

export type PopoverTrigger = "click" | "hover";

PopoverPlacement

Source: components/src/logic/popover.svelte.ts

export type PopoverPlacement = "top" | "bottom" | "left" | "right";

PopoverCloseMode

Source: components/src/logic/popover.svelte.ts

export type PopoverCloseMode = "outside" | "manual";