{"slug":"popover","name":"Popover","packageName":"@liquidify/react","category":"presentational","maturity":"stable","summary":"A floating glass bubble, anchored to the trigger that opened it, that presents free-form content (children) beside the anchor — the web analogue of SwiftUI .popover(isPresented:attachmentAnchor:arrowEdge:content:).","importPath":"@liquidify/react/popover","sourcePath":"packages/@liquidify/react/src/components/popover/popover.tsx","specPath":"packages/@liquidify/react/src/components/popover/spec.md","docsPath":"../liquidify-page/content/component-source/popover.md","documentationStatus":"stable","storyIds":["presentational-popover--default"],"additionalExports":["PopoverAlign","PopoverPlacement","PresentationalProps"],"anatomy":[{"name":"wraps the consumer's trigger element (e.g. a","description":"wraps the consumer's trigger element (e.g. a"},{"name":"is the portalled glass bubble","description":"is the portalled glass bubble"}],"props":[{"name":"align","type":"PopoverAlign | undefined","required":false,"description":"Cross-axis alignment of the bubble. Defaults to `\"center\"`.","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"aria-label","type":"string | undefined","required":false,"description":"Accessible name of the panel (default `\"Popover\"`).","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"aria-labelledby","type":"string | undefined","required":false,"description":"Id of the element labelling the panel.","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"children","type":"ReactNode","required":true,"description":"Free-form body of the bubble.","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"className","type":"string | undefined","required":false,"description":"Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"defaultOpen","type":"boolean | undefined","required":false,"description":"Uncontrolled seed for the open state (default `false`).","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"material","type":"\"frosted\" | \"regular\" | \"clear\" | undefined","required":false,"description":"Material of the glass surface — `frosted · regular · clear` (§06). Forwarded to the engine, which resolves the chain `prop ?? <Material default> ?? \"regular\"`, so a bare surface inherits the enclosing provider default.","defaultValue":"enclosing `<Material default>`, else `\"regular\"`","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"onOpenChange","type":"((open: boolean) => void) | undefined","required":false,"description":"Fires with the next open state on every open/close path (Escape, outside tap, focus-loss).","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"open","type":"boolean | undefined","required":false,"description":"Controlled open state. Passing it switches Popover to controlled mode.","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"perf","type":"\"low\" | \"high\" | undefined","required":false,"description":"Perf budget of the glass surface (ADR-0006/0007). Forwarded to the engine as `data-glass-perf`; `\"high\"` opts this surface into the edge-lensing filter. When omitted, derives from the enclosing `<Backdrop type>` (§04).","defaultValue":"derived from `<Backdrop type>`, else `\"low\"`","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"placement","type":"LogicalSide | undefined","required":false,"description":"Which side of the trigger the bubble sits on. Defaults to `\"bottom\"`.","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"},{"name":"ref","type":"Ref<HTMLDivElement> | undefined","required":false,"description":"Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"trigger","type":"ReactNode","required":true,"description":"The element that opens the popover — merged onto `Popup.Trigger` via `asChild`.","declaredIn":"packages/@liquidify/react/src/components/popover/popover.tsx"}],"swiftUIMap":[{"swiftUI":"`isPresented: Binding<Bool>`","react":"`open` / `defaultOpen` / `onOpenChange`","notes":"presentation triad (ADR-0015); controlled iff `open !== undefined` (audit §8); forwarded to `Popup` as controlled","relationship":"direct"},{"swiftUI":"(the modified view)","react":"`trigger: ReactNode`","notes":"the anchor element, merged via `Popup.Trigger asChild` (ADR-0016)","relationship":"direct"},{"swiftUI":"`content: () -> View`","react":"`children: ReactNode`","notes":"free-form body rendered inside `Popup.Content` (contrast ActionSheet, which takes structured `actions[]`, not `children`); `color=\"inherit\"` (ADR-0028)","relationship":"direct"},{"swiftUI":"`arrowEdge: Edge` (default `.top`)","react":"`placement?: \"top\" | \"bottom\" | \"left\" | \"right\"`","notes":"forwarded to `Popup`. The arrow/caret nub itself is **not rendered on web** (intentional — Popup has no caret), so `arrowEdge`'s only surviving meaning is which trigger side the panel sits on, expressed via `placement`. **Inversion (load-bearing):** SwiftUI `arrowEdge` names the *bubble* edge that faces the anchor; Popup `placement` names the *trigger* side the panel sits on, so SwiftUI `arrowEdge: .top` (bubble's top edge against the anchor, i.e. bubble below it) → React `placement: \"bottom\"`. `placement` stays Popup-native, not a copy of SwiftUI's edge naming. Default `\"bottom\"` with Popup collision auto-flip (see open questions).","relationship":"direct"},{"swiftUI":"`attachmentAnchor: .point(.center)`","react":"`align?: \"start\" | \"center\" | \"end\"` (`\"center\"` default)","notes":"Popup's cross-axis alignment; `.point(.center)` maps to centred alignment and `.rect(.bounds)` (SwiftUI default) ≈ Popup's default rect anchoring. A distinct point-vs-bounds `attachmentAnchor` prop is **not** introduced for v1 (open question).","relationship":"direct"},{"swiftUI":"`presentationCompactAdaptation(.popover)`","react":"(no prop — always anchored on web)","notes":"v1 renders Popover as an always-anchored popover; there is no compact/regular size-class swap on the web, so the \"adapts to sheet\" default is dropped. A responsive `<Sheet>` swap is deferred (Out of scope; open question).","relationship":"direct"},{"swiftUI":"(presentation chrome)","react":"`material?` / `perf?`","notes":"`PresentationalProps` (ADR-0023) — the bubble's glass surface (default `frosted`); the base is tint-free (no `tint` axis).","relationship":"direct"},{"swiftUI":"`label` on the modified view","react":"`aria-label` / `aria-labelledby`","notes":"accessible name of the dialog panel; the demo body has no heading, so a name must be supplied (default `\"Popover\"`).","relationship":"direct"},{"swiftUI":"(no SwiftUI analogue)","react":"`ref?: Ref<HTMLDivElement>`","notes":"`PopoverProps extends PresentationalProps<HTMLDivElement>` — forwarded to the semantic root, the portalled `role=\"dialog\"` glass panel; attached while mounted, `null` while closed.","relationship":"web-only"}],"tokens":["--lq-color-label-primary-","--lq-duration-","--lq-easing-","--lq-glass-","--lq-hit-target-touch","--lq-popover-min-width","--lq-popover-pad","--lq-popup-","--lq-popup-bloom-scale","--lq-popup-x","--lq-popup-y","--lq-radius-22"],"keyboard":[{"key":"Keyboard","action":"Keyboard (via Popup): Escape dismisses and returns focus to the trigger. Tab moves through focusable children within the panel; focus leaving the panel (Tab-out / programmatic blur to an outside node) closes it and restores focus to the trigger — the non-modal disclosure path (there is no focus trap; contrast a modal dialog)."}],"variants":[],"variantPages":[]}