Popover
PopoverNext shows floating content next to a target: menus, small forms and details. It positions with Floating UI and moves to stay on screen.
Usage#
import { PopoverNext } from "@clawscale/react";<PopoverNext content={<Menu>{/* items */}</Menu>} placement="bottom-start">
<Button endIcon="caret-down" text="Export" />
</PopoverNext>Pass the target as the only child and the floating content as content. PopoverNext wraps the target in a span. To render the target yourself, without a wrapper, use renderTarget and spread the props it receives onto your element.
Examples#
Basic#
A click on the target toggles the popover. Classes.POPOVER_CONTENT_SIZING pads the content. A click on an element with Classes.POPOVER_DISMISS closes it.
import { Button, Classes, H6, PopoverNext } from "@clawscale/react";
export default function PopoverBasic() {
return (
<PopoverNext
placement="bottom"
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
content={
<div>
<H6>Pause ingest-orders?</H6>
<p>Scheduled runs wait until you resume. 2 runs are queued.</p>
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
<Button className={Classes.POPOVER_DISMISS} text="Cancel" />
<Button className={Classes.POPOVER_DISMISS} intent="warning" text="Pause" />
</div>
</div>
}
>
<Button icon="pause" text="Pause pipeline" />
</PopoverNext>
);
}Placement#
placement sets the side, optionally with a -start or -end alignment. The popover flips when that side runs out of room. Leave placement unset to pick the side with the most room.
import { Button, Classes, POPOVER_NEXT_PLACEMENTS, PopoverNext } from "@clawscale/react";
export default function PopoverPlacement() {
return (
<div
style={{
display: "grid",
gap: 8,
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
width: "100%",
maxWidth: 480,
}}
>
{POPOVER_NEXT_PLACEMENTS.map((placement) => (
<PopoverNext
content={`placement="${placement}"`}
fill
key={placement}
placement={placement}
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
>
<Button text={placement} />
</PopoverNext>
))}
</div>
);
}Interaction kinds#
interactionKind sets what opens and closes the popover.
click(default): a click on the target opens it. A click anywhere else closes it.click-target: only clicks on the target open and close it.hover: opens on hover and stays open while the pointer is over the target or the popover.hover-target: opens on hover and closes when the pointer leaves the target.
import { Button, Menu, MenuItem, PopoverNext } from "@clawscale/react";
const kinds = ["click", "click-target", "hover", "hover-target"] as const;
export default function PopoverInteraction() {
const menu = (
<Menu>
<MenuItem icon="play" shouldDismissPopover={false} text="Run now" />
<MenuItem icon="pause" shouldDismissPopover={false} text="Pause schedule" />
<MenuItem icon="history" shouldDismissPopover={false} text="Run history" />
</Menu>
);
return (
<>
{kinds.map((kind) => (
<PopoverNext content={menu} interactionKind={kind} key={kind} placement="bottom">
<Button endIcon="caret-down" text={kind} />
</PopoverNext>
))}
</>
);
}Controlled#
Pass isOpen to control the popover. onInteraction reports the state the user asked for. Here submitting the form closes it.
import { Button, Classes, InputGroup, PopoverNext } from "@clawscale/react";
import { useState } from "react";
export default function PopoverControlled() {
const [isOpen, setIsOpen] = useState(false);
return (
<PopoverNext
isOpen={isOpen}
onInteraction={(nextOpen) => setIsOpen(nextOpen)}
placement="bottom"
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
content={
<form
style={{ display: "grid", gap: 12, width: 240 }}
onSubmit={(event) => {
event.preventDefault();
setIsOpen(false);
}}
>
<InputGroup aria-label="Dataset name" defaultValue="orders_daily" />
<Button intent="primary" text="Rename dataset" type="submit" />
</form>
}
>
<Button icon="edit" text="Rename" />
</PopoverNext>
);
}Minimal#
arrow={false} removes the arrow and animation="minimal" makes the transition subtle. Use both for popovers that open often, such as menus.
import { Button, Classes, PopoverNext } from "@clawscale/react";
const freshness = "orders_daily was refreshed 4 min ago.";
export default function PopoverMinimal() {
return (
<>
<PopoverNext content={freshness} placement="bottom" popoverClassName={Classes.POPOVER_CONTENT_SIZING}>
<Button text="Default" />
</PopoverNext>
<PopoverNext
animation="minimal"
arrow={false}
content={freshness}
placement="bottom"
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
>
<Button text="Minimal" />
</PopoverNext>
</>
);
}Dropdown menu#
Put a Menu in a minimal popover. A click on a menu item closes the popover, unless the item sets shouldDismissPopover={false}.
import { Button, Menu, MenuDivider, MenuItem, PopoverNext } from "@clawscale/react";
export default function PopoverDropdown() {
return (
<PopoverNext
animation="minimal"
arrow={false}
placement="bottom-start"
content={
<Menu>
<MenuItem icon="export" text="Export CSV" />
<MenuItem icon="export" text="Export Parquet" />
<MenuItem icon="clipboard" text="Copy as SQL" />
<MenuDivider />
<MenuItem icon="share" text="Share view" />
</Menu>
}
>
<Button endIcon="caret-down" icon="export" text="Export" />
</PopoverNext>
);
}Match target width#
matchTargetWidth makes the popover as wide as its target. fill stretches the target to its container.
import { Button, Menu, MenuItem, PopoverNext } from "@clawscale/react";
import { useState } from "react";
const regions = ["us-east-1", "us-west-2", "eu-west-1", "ap-south-1"];
export default function PopoverMatchTargetWidth() {
const [region, setRegion] = useState("eu-west-1");
return (
<div style={{ width: "100%", maxWidth: 280 }}>
<PopoverNext
animation="minimal"
arrow={false}
fill
matchTargetWidth
placement="bottom-start"
content={
<Menu>
{regions.map((name) => (
<MenuItem active={name === region} key={name} text={name} onClick={() => setRegion(name)} />
))}
</Menu>
}
>
<Button alignText="start" endIcon="caret-down" icon="globe" text={region} />
</PopoverNext>
</div>
);
}Interactive playground#
Props interface#
interface PopoverNextProps@clawscale/reactProps interface for PopoverNext component.
| Props | Description |
|---|---|
animation | PopoverAnimation = "scale"The animation style to use for the popover. |
arrow | boolean = trueWhether to show the arrow pointing to the target. |
autoFocus | boolean = true for click interactions, false for hover interactionsWhether the popover/tooltip should acquire application focus when it first opens. |
autoUpdateOptions | PopoverNextAutoUpdateOptionsOptions forwarded to floating-ui's autoUpdate to configure how the popover position is automatically updated while it is open. By default, all autoUpdate behaviors are enabled (ancestorScroll, ancestorResize, elementResize, and layoutShift). |
backdropProps | React.HTMLProps<HTMLDivElement>HTML props for the backdrop element. Can be combined with backdropClassName. |
boundary | PopoverNextBoundaryA boundary element supplied to the positioning middleware. This is a shorthand for overriding Floating UI middleware options with the middleware prop. |
canEscapeKeyClose | booleanWhether the popover can be closed by pressing the Escape key. |
captureDismiss | boolean = falseWhen enabled, clicks inside a Classes.POPOVER_DISMISS element will only close the current popover and not outer popovers. When disabled, the current popover and any ancestor popovers will be closed. |
children | React.ReactNodeInteractive element which will trigger the popover. |
className | stringA space-delimited string of class names applied to the popover's target wrapper element. |
content | string | React.JSX.ElementThe content displayed inside the popover. |
defaultIsOpen | boolean = falseInitial opened state when uncontrolled. |
disabled | boolean = falsePrevents the popover from appearing when true. |
enforceFocus | booleanWhether the popover should enforce focus within itself. |
fill | booleanWhether the wrapper and target should take up the full width of their container. Note that supplying true for this prop will force targetTagName="div". |
hasBackdrop | boolean = falseEnables an invisible overlay beneath the popover that captures clicks and prevents interaction with the rest of the document until the popover is closed. This prop is only available when interactionKind is PopoverInteractionKind.CLICK. When popovers with backdrop are opened, they become focused. |
hoverCloseDelay | number = 300The amount of time in milliseconds the popover should remain open after the user hovers off the trigger. The timer is canceled if the user mouses over the target before it expires. |
hoverOpenDelay | number = 150The amount of time in milliseconds the popover should wait before opening after the user hovers over the trigger. The timer is canceled if the user mouses away from the target before it expires. |
inheritDarkTheme | boolean = trueWhether a popover that uses a Portal should automatically inherit the dark theme from its parent. |
interactionKind | PopoverInteractionKind = "click"The kind of interaction that triggers the display of the popover. |
isOpen | boolean = undefinedWhether the popover is visible. Passing this prop puts the popover in controlled mode, where the only way to change visibility is by updating this property. If disabled={true}, this prop will be ignored, and the popover will remain closed. |
lazy | booleanWhether the popover should be rendered lazily. |
matchTargetWidth | boolean = falseWhether the popover content should be sized to match the width of the target. This is sometimes useful for dropdown menus. |
middleware | MiddlewareConfigConfig for Floating UI middlewares. Each config is a partial options object, keyed by its middleware name. |
onClose | (event?: React.SyntheticEvent<HTMLElement>) => voidCallback invoked when the popover is closed. |
onClosed | (node: HTMLElement) => voidCallback invoked when the popover has finished closing. |
onClosing | (node: HTMLElement) => voidCallback invoked when the popover is closing. |
onInteraction | (nextOpenState: boolean, e?: React.SyntheticEvent<HTMLElement>) => voidCallback invoked in controlled mode when the popover open state would change due to user interaction. |
onOpened | (node: HTMLElement) => voidCallback invoked when the popover has finished opening. |
onOpening | (node: HTMLElement) => voidCallback invoked when the popover is opening. |
openOnTargetFocus | boolean = trueWhether the popover should open when its target is focused. If true, target will render with tabindex="0" to make it focusable via keyboard navigation. |
placement | PopoverNextPlacementThe placement (relative to the target) at which the popover should appear. Mutually exclusive with position prop. Prefer using this over position, as it more closely aligns with Floating UI semantics. |
popoverClassName | stringA space-delimited string of class names applied to the popover element. |
popoverRef | React.Ref<HTMLDivElement>DOM ref attached to the Classes.POPOVER element. |
popupKind | PopupKind = "menu" or undefinedThe kind of popup displayed by the popover. Gets directly applied to the aria-haspopup attribute of the target element. This property is ignored if interactionKind is PopoverInteractionKind.HOVER_TARGET_ONLY. |
portalClassName | stringA space-delimited string of class names applied to the portal element. |
portalContainer | HTMLElementThe container element into which the popover content is rendered. |
positioningStrategy | PopoverNextPositioningStrategy = "absolute"The CSS position strategy for the floating popover element. |
renderTarget | (props: PopoverRenderTargetProps & PopoverHoverTargetHandlers<T> & PopoverClickTargetHandlers<T>) => React.JSX.ElementTarget renderer which receives props injected by Popover which should be spread onto the rendered element. This function should return a single React node. |
rootBoundary | PopoverNextRootBoundaryA root boundary element supplied to the positioning middleware. This is a shorthand for overriding Floating UI middleware options with the middleware prop. |
shouldReturnFocusOnClose | boolean = trueWhether the application should return focus to the last active element in the document after this popover closes. |
targetProps | THTML props for the target element. This is useful in some cases where you need to render some simple attributes on the generated target element. |
targetTagName | keyof React.JSX.IntrinsicElements = "span" ("div" if `fill={true}`)HTML tag name for the target element. This must be an HTML element to ensure that it supports the necessary DOM event handlers. |
transitionDuration | numberThe duration of the popover's transition in milliseconds. |
usePortal | boolean = trueWhether the popover should be rendered inside a Portal attached to portalContainer prop. |
Legacy Popover#
Blueprint deprecated Popover in favor of PopoverNext. It positions content with Popper.js and places it in the wrong spot under React 19. Blueprint's own Tooltip, MenuItem and Select already use PopoverNext.
To migrate, rename these props:
| Popover | PopoverNext |
|---|---|
position="bottom-left" | placement="bottom-start" |
minimal | arrow={false} and animation="minimal" |
modifiers | middleware |
shouldReturnFocusOnClose now defaults to true. popoverPropsToNextProps converts a legacy props object.