Skip to content

Popover

A click-triggered disclosure panel for rich or interactive content, such as a filter form, a settings menu, or extra detail. It is non-modal, with no focus trap and no backdrop: reach for Modal when you need a focus-trapped dialog, or Dropdown when you need a menu of actions.

Import

import { Popover } from "@hyzer-labs/ui"

Demo

Click the trigger to open the panel. Controls inside work normally, and Tab moves through them and back out again, with no focus trap. Click outside, or press Escape, to close.

let showFinished = $state(true);
let showUpcoming = $state(true);

<Popover triggerLabel="Filters">
	{#snippet triggerIcon()}<IconSettings />{/snippet}
	<Stack gap="sm">
		<Checkbox name="finished" label="Finished rounds" bind:checked={showFinished} />
		<Checkbox name="upcoming" label="Upcoming tee times" bind:checked={showUpcoming} />
	</Stack>
</Popover>

Props

NameTypeDefaultNote
openbooleanfalse$bindable.
placement'top' | 'bottom' | 'left' | 'right' | '<side>-start' | '<side>-end''bottom-start'A bare side centers on the trigger; -start/-end add alignment. left/right follow the trigger's writing direction, so RTL flips the physical side. See Positioning for how a placement resolves.
offsetnumber8Gap from the trigger, in px.
autoFocusbooleanfalsetrue moves focus to the panel's first focusable element when it opens. Off by default, because a disclosure should not steal focus.
dismissiblebooleantruefalse turns off closing on an outside click. Escape still closes the panel either way; there is no opt-out.
labelstringThe panel's accessible name, used when it has no visible heading of its own.
onopen() => void
onclose() => void
triggerLabelstringVisible label for the default composed-Button trigger. Ignored when `trigger` is provided.
triggerPropsPopoverTriggerProps{}Trigger appearance pass-through. Resolves to variant: 'outline', intent: 'neutral'.
triggerIconSnippetIcon for the default Button trigger.
triggerSnippet<[TriggerAttrs]>Escape hatch. Wins over triggerLabel/triggerProps/triggerIcon. Receives an attrs bag (id, aria-expanded, aria-controls, popovertarget, onclick) to spread onto your own element: a link, an avatar, a custom control.
childrenSnippetRequired. The panel content. Interactive controls are supported.
classstringMerged after the hz-popover class.

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-popover

Data attributes

HookValuesStyles
data-openpresent while openOn the root — the Dropdown parity hook.
data-state (panel)'open' | 'closed'On .hz-popover-panel — mirrors the open prop; the hook to animate the entrance against.
data-placementPlacementOn .hz-popover-panel — the requested placement string, e.g. "bottom-start".
data-side'top' | 'bottom' | 'left' | 'right'On .hz-popover-panel — the resolved, physical side. See Positioning for the caret recipe.
data-align'start' | 'center' | 'end'On .hz-popover-panel — the resolved cross-axis alignment (RTL-aware for a top/bottom placement).

Custom properties

HookValuesStyles
--hz-z-popover<number> — default 200Matters mainly on the non-top-layer fallback path (older browsers) — a real top-layer panel stacks above everything regardless of z-index.

Part classes

HookValuesStyles
.hz-popover-triggeron a ButtonMerged onto the default composed Button trigger — absent when the `trigger` snippet escape hatch is used instead, since that renders your own element.
.hz-popover-panelchild elementThe disclosure panel surface.
.hz-popover-contentchild elementThe scroll container inside the panel — the panel itself stays overflow-visible so a caret you draw can protrude. See Positioning for the recipe.

Accessibility

The trigger carries aria-expanded and aria-controls pointing at the panel. That is the APG Disclosure pattern, not a dialog. The panel itself is a plain region by default, or a labeled role="group" when you pass label (for a panel with no visible heading). It is never aria-modal, never traps focus, and never renders a backdrop. Interactive content inside flows through the normal tab order and back out.

Escape always closes the panel and returns focus to the trigger, even with dismissible: false. There is no opt-out, the same rule Modal follows for its own Escape handling. Clicking outside the panel closes it too, unless dismissible: false. Unlike Escape, an outside click leaves focus wherever the click landed rather than pulling it back.

By default (autoFocus: false) opening the panel does not move focus at all, because a disclosure should not take focus uninvited. Set autoFocus when the panel's first control really is the next thing someone wants, such as a search field.

References: APG Disclosure (Show/Hide) pattern · MDN: Popover API · WCAG 2.2 SC 1.4.13: Content on Hover or Focus