Skip to main content
On this pageFunctions

Ui/Popover

Functions

close

functionsource
/**
 * Programmatically closes the popover, updating the model and returning
 *  focus and modal commands plus a `Closed` OutMessage when it was open.
 */
(model: Popover.Model): UpdateReturn

init

functionsource
/** Creates an initial popover model from a config. Defaults to closed. */
(config: InitConfig): Popover.Model

open

functionsource
/**
 * Programmatically opens the popover, updating the model and returning
 *  focus and modal commands plus an `Opened` OutMessage.
 */
(model: Popover.Model): UpdateReturn

update

functionsource

Types

BlurredPanel

typesource
/** Sent when the popover panel loses focus. Does NOT return focus to the button. */
type BlurredPanel = CallableTaggedStruct<"BlurredPanel", {}>

IgnoredMouseClick

typesource
/** Sent when a mouse click on the button is ignored because pointer-down already handled the toggle. */
type IgnoredMouseClick = CallableTaggedStruct<"IgnoredMouseClick", {}>

InitConfig

typesource
/** Configuration for creating a popover model with `init`. `isAnimated` enables animation coordination (default `false`). `isModal` locks page scroll and inerts other elements when open (default `false`). `contentFocus` hands focus ownership to the consumer. The panel is not focusable and does not close on blur, so the consumer must focus a descendant on open and close the popover on its own blur rules (default `false`). */
type InitConfig = Readonly<{
  contentFocus: boolean
  id: string
  isAnimated: boolean
  isModal: boolean
}>

PressedPointerOnButton

typesource
/** Sent when the user presses a pointer device on the popover button. Records pointer type and toggles for mouse. */
type PressedPointerOnButton = CallableTaggedStruct<"PressedPointerOnButton", {
  button: Number
  pointerType: String
}>

RenderInfo

typesource
/**
 * Render-time payload published to the consumer's `toView`.
 * 
 *  - `button`: attribute bundle for the trigger button.
 *  - `panel`: attribute bundle for the floating panel. Includes the
 *    anchor Mount that positions the panel via Floating UI, ARIA
 *    linkage to the button, and panel keydown/blur handlers.
 *  - `backdrop`: attribute bundle for the modal backdrop. Includes the
 *    portal Mount that moves the backdrop to document.body. The
 *    backdrop's OnClick closes the popover.
 *  - `isVisible`: derived from `isOpen` and the Animation
 *    `transitionState`. The consumer renders the panel + backdrop only
 *    while this is true.
 */
type RenderInfo = Readonly<{
  backdrop: ReadonlyArray<ChildAttribute>
  button: ReadonlyArray<ChildAttribute>
  isVisible: boolean
  panel: ReadonlyArray<ChildAttribute>
}>

SuppressedSpaceScroll

typesource
/** Sent when a Space key-up is captured to prevent page scrolling. */
type SuppressedSpaceScroll = CallableTaggedStruct<"SuppressedSpaceScroll", {}>

ViewInputs

typesource
/** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
type ViewInputs = Readonly<{
  anchor: AnchorConfig
  focusSelector: string
  isDisabled: boolean
  toView: (render: RenderInfo) => Html
}>

Constants

AnchorPopover

constsource
/**
 * The anchor-positioning Mount this Popover renders on its panel. Exposed so
 *  Scene tests can call `Scene.Mount.resolve(AnchorPopover, CompletedAnchorPopover())`
 *  to acknowledge the mount produced by the rendered panel.
 */
const AnchorPopover: MountDefinitionWithArgs<"AnchorPopover", {
  anchor: Struct<{
    gap: optional<Number>
    offset: optional<Number>
    padding: optional<Number>
    placement: optional<Literals<readonly ["top", "right", "bottom", "left", "top-start", "top-end", "right-start", "right-end", "bottom-start", "bottom-end", "left-start", "left-end"]>>
    portal: optional<Boolean>
  }>
  buttonId: String
  focusSelector: optional<String>
}, {
  _tag: "CompletedAnchorPopover"
}>

Closed

constsource
/** Sent to the parent after the popover transitions to its closed state. */
const Closed: CallableTaggedStruct<"Closed", {}>

CompletedAnchorPopover

constsource
/** Sent when the popover panel mounts and Floating UI has positioned it. Update no-ops; the side effect is the act of positioning, surfaced for DevTools observability. */
const CompletedAnchorPopover: CallableTaggedStruct<"CompletedAnchorPopover", {}>

CompletedFocusButton

constsource
/** Sent when the focus-button command completes after closing. */
const CompletedFocusButton: CallableTaggedStruct<"CompletedFocusButton", {}>

CompletedFocusPanel

constsource
/** Sent when the focus-panel command completes after opening the popover. */
const CompletedFocusPanel: CallableTaggedStruct<"CompletedFocusPanel", {}>

CompletedInertOthers

constsource
/** Sent when the inert-others command completes. */
const CompletedInertOthers: CallableTaggedStruct<"CompletedInertOthers", {}>

CompletedLockScroll

constsource
/** Sent when the scroll lock command completes. */
const CompletedLockScroll: CallableTaggedStruct<"CompletedLockScroll", {}>

CompletedPortalPopoverBackdrop

constsource
/** Sent when the popover backdrop mounts and is portaled to the document body. Update no-ops; surfaces the portal side effect for DevTools. */
const CompletedPortalPopoverBackdrop: CallableTaggedStruct<"CompletedPortalPopoverBackdrop", {}>

CompletedRestoreInert

constsource
/** Sent when the restore-inert command completes. */
const CompletedRestoreInert: CallableTaggedStruct<"CompletedRestoreInert", {}>

CompletedUnlockScroll

constsource
/** Sent when the scroll unlock command completes. */
const CompletedUnlockScroll: CallableTaggedStruct<"CompletedUnlockScroll", {}>

DetectMovementOrAnimationEnd

constsource
/** Detects whether the popover button moved or the leave animation ended. Whichever comes first; both outcomes signal the Animation submodel that leave is complete. */
const DetectMovementOrAnimationEnd: CommandDefinitionWithArgs<"DetectMovementOrAnimationEnd", {
  id: String
}, Effect<{
  _tag: "GotAnimationMessage"
  message: {
    _tag: "Showed"
  } | {
    _tag: "Hid"
  } | {
    _tag: "AdvancedAnimationFrame"
  } | {
    _tag: "EndedAnimation"
  }
}, never, never>>

FocusButton

constsource
/** Moves focus back to the popover button after closing. */
const FocusButton: CommandDefinitionWithArgs<"FocusButton", {
  id: String
}, Effect<{
  _tag: "CompletedFocusButton"
}, never, never>>

FocusPanel

constsource
/** Moves focus to the popover panel after opening. */
const FocusPanel: CommandDefinitionWithArgs<"FocusPanel", {
  id: String
}, Effect<{
  _tag: "CompletedFocusPanel"
}, never, never>>

GotAnimationMessage

constsource
/** Wraps an Animation submodel message for delegation. */
const GotAnimationMessage: CallableTaggedStruct<"GotAnimationMessage", {
  message: Union<[CallableTaggedStruct<"Showed", {}>, CallableTaggedStruct<"Hid", {}>, CallableTaggedStruct<"AdvancedAnimationFrame", {}>, CallableTaggedStruct<"EndedAnimation", {}>]>
}>

InertOthers

constsource
/** Marks all elements outside the popover as inert for modal behavior. */
const InertOthers: CommandDefinitionWithArgs<"InertOthers", {
  id: String
}, Effect<{
  _tag: "CompletedInertOthers"
}, never, never>>

LockScroll

constsource
/** Prevents page scrolling while the popover is open in modal mode. */
const LockScroll: CommandDefinitionNoArgs<"LockScroll", Effect<{
  _tag: "CompletedLockScroll"
}, never, never>>

Message

constsource
/** Union of all messages the popover component can produce. */
const Message: S.Union<[typeof RequestedOpen, typeof RequestedClose, typeof BlurredPanel, typeof PressedPointerOnButton, typeof CompletedFocusPanel, typeof CompletedFocusButton, typeof CompletedLockScroll, typeof CompletedUnlockScroll, typeof CompletedInertOthers, typeof CompletedRestoreInert, typeof IgnoredMouseClick, typeof SuppressedSpaceScroll, typeof CompletedAnchorPopover, typeof CompletedPortalPopoverBackdrop, typeof GotAnimationMessage]>

Model

constsource
/** Schema for the popover component's state, tracking open/closed status and animation lifecycle. */
const Model: Struct<{
  animation: Struct<{
    id: String
    isShowing: Boolean
    transitionState: Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>
  }>
  contentFocus: Boolean
  id: String
  isAnimated: Boolean
  isModal: Boolean
  isOpen: Boolean
  maybeLastButtonPointerType: Option<String>
}>

Opened

constsource
/** Sent to the parent after the popover transitions to its open state. Fires once `update` has processed `RequestedOpen` and `isOpen` reflects the new state. */
const Opened: CallableTaggedStruct<"Opened", {}>

OutMessage

constsource
/** Union of out-messages the popover component can produce. Parents reacting to open/close transitions (e.g. to reset related state, fire analytics) read this from the third element of `update`'s return tuple. */
const OutMessage: Union<readonly [CallableTaggedStruct<"Opened", {}>, CallableTaggedStruct<"Closed", {}>]>

PortalPopoverBackdrop

constsource
/**
 * The backdrop-portaling Mount this Popover renders. Exposed so Scene tests can
 *  call `Scene.Mount.resolve(PortalPopoverBackdrop, CompletedPortalPopoverBackdrop())` to
 *  acknowledge the mount produced by the rendered backdrop.
 */
const PortalPopoverBackdrop: MountDefinitionNoArgs<"PortalPopoverBackdrop", {
  _tag: "CompletedPortalPopoverBackdrop"
}>

RequestedClose

constsource
/** Sent when the popover should close via Escape key or backdrop click. Returns focus to the button. */
const RequestedClose: CallableTaggedStruct<"RequestedClose", {}>

RequestedOpen

constsource
/** Sent when the popover should open via button click or keyboard activation. */
const RequestedOpen: CallableTaggedStruct<"RequestedOpen", {}>

RestoreInert

constsource
/** Removes the inert attribute from elements outside the popover. */
const RestoreInert: CommandDefinitionWithArgs<"RestoreInert", {
  id: String
}, Effect<{
  _tag: "CompletedRestoreInert"
}, never, never>>

UnlockScroll

constsource
/** Re-enables page scrolling after the popover closes. */
const UnlockScroll: CommandDefinitionNoArgs<"UnlockScroll", Effect<{
  _tag: "CompletedUnlockScroll"
}, never, never>>

view

constsource
/** Renders a headless popover with a trigger button and a floating panel. */
const view: SubmodelView<Popover.Model, {
  _tag: "CompletedLockScroll"
} | {
  _tag: "CompletedUnlockScroll"
} | {
  _tag: "CompletedInertOthers"
} | {
  _tag: "CompletedRestoreInert"
} | {
  _tag: "CompletedFocusButton"
} | {
  _tag: "IgnoredMouseClick"
} | {
  _tag: "SuppressedSpaceScroll"
} | {
  _tag: "RequestedOpen"
} | {
  _tag: "RequestedClose"
} | {
  _tag: "BlurredPanel"
} | {
  _tag: "PressedPointerOnButton"
  button: number
  pointerType: string
} | {
  _tag: "CompletedFocusPanel"
} | {
  _tag: "CompletedAnchorPopover"
} | {
  _tag: "CompletedPortalPopoverBackdrop"
} | {
  _tag: "GotAnimationMessage"
  message: {
    _tag: "Showed"
  } | {
    _tag: "Hid"
  } | {
    _tag: "AdvancedAnimationFrame"
  } | {
    _tag: "EndedAnimation"
  }
}, Readonly<{
  anchor: {
    gap: number
    offset: number
    padding: number
    placement: "bottom" | "left" | "right" | "top" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end"
    portal: boolean
  }
  focusSelector: string
  isDisabled: boolean
  toView: (render: RenderInfo) => Html
}>>

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson