Skip to main content
On this pageFunctions

Ui/Popover

Functions

buttonId

functionsource
/**
 * Returns the bare DOM id of the popover trigger button, derived from the
 *  popover's base id. Use this to associate an external label with the
 *  trigger via a native `<label for={Popover.buttonId(id)}>` or an
 *  `aria-labelledby` reference.
 */
(id: string): string

close

functionsource
/**
 * Programmatically closes the popover. When it was open, updates the Model
 *  and returns focus and modal Commands plus a `Closed` OutMessage. When it
 *  was already closed, it is a no-op: no Commands and no OutMessage.
 */
(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

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
}>

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>
}>

ViewInputs

typesource
/** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
type ViewInputs = Readonly<{
  anchor: AnchorConfig
  ariaLabel: string
  ariaLabelledBy: string
  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>
    isPlacementLocked: optional<Boolean>
    offset: optional<Number>
    padding: optional<Union<readonly [
      Number,
      Struct<{
        bottom: optionalKey<Number>
        left: optionalKey<Number>
        right: optionalKey<Number>
        top: optionalKey<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"
}>

BlurredPanel

constsource
/** Sent when the popover panel loses focus. Does NOT return focus to the button. */
const BlurredPanel: CallableTaggedStruct<"BlurredPanel", {}>

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: "CompletedWaitForPaint"
  } | {
    _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<"CompletedWaitForPaint", {}>, CallableTaggedStruct<"EndedAnimation", {}>]>
}>

IgnoredMouseClick

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

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"
}>

PressedPointerOnButton

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

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>>

SuppressedSpaceScroll

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

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: "RequestedOpen"
} | {
  _tag: "RequestedClose"
} | {
  _tag: "BlurredPanel"
} | {
  _tag: "PressedPointerOnButton"
  button: number
  pointerType: string
} | {
  _tag: "IgnoredMouseClick"
} | {
  _tag: "SuppressedSpaceScroll"
} | {
  _tag: "CompletedFocusPanel"
} | {
  _tag: "CompletedFocusButton"
} | {
  _tag: "CompletedLockScroll"
} | {
  _tag: "CompletedUnlockScroll"
} | {
  _tag: "CompletedInertOthers"
} | {
  _tag: "CompletedRestoreInert"
} | {
  _tag: "CompletedAnchorPopover"
} | {
  _tag: "CompletedPortalPopoverBackdrop"
} | {
  _tag: "GotAnimationMessage"
  message: {
    _tag: "Showed"
  } | {
    _tag: "Hid"
  } | {
    _tag: "CompletedWaitForPaint"
  } | {
    _tag: "EndedAnimation"
  }
}, Readonly<{
  anchor: Anchor.AnchorConfig
  ariaLabel: string
  ariaLabelledBy: string
  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