You are looking at the documentation of the work in progress Hope UI 1.0, examples and information may be broken or outdated.

Overlays

Popover

Display a non-modal dialog that floats around a trigger.

Import

tsx
import { Popover } from "@hope-ui/core";
tsx
import { Popover } from "@hope-ui/core";
  • Popover: The wrapper that provides props, state, and context to its children.
  • Popover.Trigger: The component that opens/closes the popover.
  • Popover.Anchor: The component to use as positioning reference instead of the trigger.
  • Popover.Content: The popover itself.
  • Popover.CloseButton: A button to close the popover.
  • Popover.Heading: An optional heading for the popover.
  • Popover.Description: An optional description for the popover.

Usage

Popover.Trigger renders an unstyled button by default. For accessibility reason, when using the as prop ensure the element passed in is focusable.

tsx
<Popover>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Trigger mode

Use the triggerMode prop to change the way of opening the popover. You can set the value to click (default) or hover.

tsx
<Popover triggerMode="hover">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover triggerMode="hover">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Initial focus

When the popover opens, focus is automatically set on the first focusable element in Popover.Content.

Use the data-autofocus attribute on any element to mark it as the initial focus element.

tsx
<Popover>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<HStack spacing={4}>
<Button _focus={{ color: "red" }}>Button 1 </Button>
<Button data-autofocus _focus={{ color: "red" }}>
Button 2
</Button>
</HStack>
</Popover.Content>
</Popover>
tsx
<Popover>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<HStack spacing={4}>
<Button _focus={{ color: "red" }}>Button 1 </Button>
<Button data-autofocus _focus={{ color: "red" }}>
Button 2
</Button>
</HStack>
</Popover.Content>
</Popover>

You can also use a different css selector by using the initialFocusSelector prop.

tsx
<Popover initialFocusSelector="#initial-focus">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<HStack spacing={4}>
<Button _focus={{ color: "red" }}>Button 1 </Button>
<Button id="initial-focus" _focus={{ color: "red" }}>
Button 2
</Button>
</HStack>
</Popover.Content>
</Popover>
tsx
<Popover initialFocusSelector="#initial-focus">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<HStack spacing={4}>
<Button _focus={{ color: "red" }}>Button 1 </Button>
<Button id="initial-focus" _focus={{ color: "red" }}>
Button 2
</Button>
</HStack>
</Popover.Content>
</Popover>

If the popover contains no focusable element, focus is set to the Popover.Content itself.

Restore focus

When the popover closes, focus is set back to the trigger element. Use the restoreFocusSelector prop and pass in a css selector to target another element.

tsx
<HStack spacing={4}>
<Popover restoreFocusSelector="[data-finalfocus]">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
<Button data-finalfocus _focus={{ color: "red" }}>
Final focus element
</Button>
</HStack>
tsx
<HStack spacing={4}>
<Popover restoreFocusSelector="[data-finalfocus]">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
<Button data-finalfocus _focus={{ color: "red" }}>
Final focus element
</Button>
</HStack>

Focus trap

Use the trapFocus prop to lock focus inside the Popover.Content.

tsx
<Popover trapFocus>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<HStack spacing={4}>
<Button>Button 1</Button>
<Button>Button 2</Button>
</HStack>
</Popover.Content>
</Popover>
tsx
<Popover trapFocus>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<HStack spacing={4}>
<Button>Button 1</Button>
<Button>Button 2</Button>
</HStack>
</Popover.Content>
</Popover>

Heading and description

Use Popover.Heading to add an accessible heading to the popover, it renders an h2 by default.

Similarly Popover.Description is used add an accessible description and renders a p by default.

Under the hood, Hope UI will set the correct aria-labelledby and aria-describedby attributes.

tsx
<Popover>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<Popover.Heading fontWeight="semibold">Title</Popover.Heading>
<Popover.Description fontSize="sm" color={{ light: "neutral.600", dark: "neutral.300" }} mb={4}>
Description
</Popover.Description>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<Popover.Heading fontWeight="semibold">Title</Popover.Heading>
<Popover.Description fontSize="sm" color={{ light: "neutral.600", dark: "neutral.300" }} mb={4}>
Description
</Popover.Description>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Same width

Use hasSameWidth prop to make the popover take the same width as the trigger element.

tsx
<Popover hasSameWidth>
<Popover.Trigger as={Button} w={96}>
Trigger
</Popover.Trigger>
<Popover.Content p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover hasSameWidth>
<Popover.Trigger as={Button} w={96}>
Trigger
</Popover.Trigger>
<Popover.Content p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Arrow

Use the arrowSize prop to change the popover's arrow size and the offset prop to adjust the distance with the trigger.

tsx
<Popover arrowSize={30} offset={15}>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover arrowSize={30} offset={15}>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

You can also remove the arrow by setting the withArrow prop to false.

tsx
<Popover withArrow={false} offset={8}>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover withArrow={false} offset={8}>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Placement

Use the placement prop to set the preferred popover placement. You can set the value to top, right, left, bottom and their -start or -end variants.

tsx
<Popover placement="top-start">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover placement="top-start">
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Even though you specified the placement, Popover will try to reposition itself in the event that available space at the specified placement isn't enough.

Open/close transitions

The transitionOptions prop allows to customize the popover open/close transitions. You can use predefined transitions or create custom ones.

tsx
<Popover
transitionOptions={{
transition: "slide-up",
duration: 400,
exitDuration: 250,
easing: "ease-out",
exitEasing: "ease-in",
}}
>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover
transitionOptions={{
transition: "slide-up",
duration: 400,
exitDuration: 250,
easing: "ease-out",
exitEasing: "ease-in",
}}
>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

To learn more about the transition API check out the createTransition documentation.

Popover anchor

Use the Popover.Anchor component if you want to use a different element for the popover positioning reference, it rendes a div by default.

In the example below, the Button is the trigger that opens/closes the popover and the Icon is the reference element that the popover will position relative to.

tsx
<Popover>
<HStack spacing={4}>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Anchor as={TargetIcon} boxSize={6} />
</HStack>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
tsx
<Popover>
<HStack spacing={4}>
<Popover.Trigger as={Button}>Trigger</Popover.Trigger>
<Popover.Anchor as={TargetIcon} boxSize={6} />
</HStack>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>

Using virtual element

The getAnchorRect prop allows to use a virtual element as popover's anchor.

The below example shows how to make a popover that follow user's cursor.

Hover the area.
tsx
function FollowCursorExample() {
const [anchorRect, setAnchorRect] = createSignal({ x: 0, y: 0 });
const [isOpen, setIsOpen] = createSignal(false);
return (
<>
<Center
height="100px"
rounded="sm"
borderWidth="2px"
borderStyle="dashed"
borderColor="neutral.300"
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
onMouseMove={event => {
event.preventDefault();
setAnchorRect({ x: event.clientX, y: event.clientY });
}}
>
Hover the area.
</Center>
<Popover isOpen={isOpen()} onOpenChange={setIsOpen} getAnchorRect={anchorRect}>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
</>
);
}
tsx
function FollowCursorExample() {
const [anchorRect, setAnchorRect] = createSignal({ x: 0, y: 0 });
const [isOpen, setIsOpen] = createSignal(false);
return (
<>
<Center
height="100px"
rounded="sm"
borderWidth="2px"
borderStyle="dashed"
borderColor="neutral.300"
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
onMouseMove={event => {
event.preventDefault();
setAnchorRect({ x: event.clientX, y: event.clientY });
}}
>
Hover the area.
</Center>
<Popover isOpen={isOpen()} onOpenChange={setIsOpen} getAnchorRect={anchorRect}>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
</>
);
}

You can also pair it with the createInteractOutside primitive to create a context menu like behavior.

Right click in the area.
tsx
function RightClickExample() {
let containerRef: HTMLDivElement | undefined;
const [anchorRect, setAnchorRect] = createSignal({ x: 0, y: 0 });
const [isOpen, setIsOpen] = createSignal(false);
createInteractOutside(
{
onInteractOutside: () => setIsOpen(false),
},
() => containerRef
);
return (
<>
<Center
ref={containerRef}
height="100px"
rounded="sm"
borderWidth="2px"
borderStyle="dashed"
borderColor="neutral.300"
onContextMenu={event => {
event.preventDefault();
setAnchorRect({ x: event.clientX, y: event.clientY });
setIsOpen(true);
}}
>
Right click in the area.
</Center>
<Popover isOpen={isOpen()} onOpenChange={setIsOpen} getAnchorRect={anchorRect}>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
</>
);
}
tsx
function RightClickExample() {
let containerRef: HTMLDivElement | undefined;
const [anchorRect, setAnchorRect] = createSignal({ x: 0, y: 0 });
const [isOpen, setIsOpen] = createSignal(false);
createInteractOutside(
{
onInteractOutside: () => setIsOpen(false),
},
() => containerRef
);
return (
<>
<Center
ref={containerRef}
height="100px"
rounded="sm"
borderWidth="2px"
borderStyle="dashed"
borderColor="neutral.300"
onContextMenu={event => {
event.preventDefault();
setAnchorRect({ x: event.clientX, y: event.clientY });
setIsOpen(true);
}}
>
Right click in the area.
</Center>
<Popover isOpen={isOpen()} onOpenChange={setIsOpen} getAnchorRect={anchorRect}>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
</>
);
}

Accessing internal state

Popover provides access to its internal isOpen state and a close method that you can access via a render prop.

tsx
<Popover>
{({ isOpen, close }) => (
<>
<Popover.Trigger as={Button}>Click to {isOpen() ? "close" : "open"}</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
<Button onClick={close}>Close</Button>
</Popover.Content>
</>
)}
</Popover>
tsx
<Popover>
{({ isOpen, close }) => (
<>
<Popover.Trigger as={Button}>Click to {isOpen() ? "close" : "open"}</Popover.Trigger>
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
<Button onClick={close}>Close</Button>
</Popover.Content>
</>
)}
</Popover>

Controlled popover

Use the isOpen and onOpenChange props to control the popover state.

tsx
function ControlledPopoverExample() {
const [isOpen, setIsOpen] = createSignal(false);
return (
<HStack spacing={4}>
<Button onClick={() => setIsOpen(prev => !prev)}>
Click to {isOpen() ? "close" : "open"}
</Button>
<Popover isOpen={isOpen()} onOpenChange={setIsOpen} closeOnBlur={false}>
<Popover.Anchor as={TargetIcon} boxSize={6} />
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
</HStack>
);
}
tsx
function ControlledPopoverExample() {
const [isOpen, setIsOpen] = createSignal(false);
return (
<HStack spacing={4}>
<Button onClick={() => setIsOpen(prev => !prev)}>
Click to {isOpen() ? "close" : "open"}
</Button>
<Popover isOpen={isOpen()} onOpenChange={setIsOpen} closeOnBlur={false}>
<Popover.Anchor as={TargetIcon} boxSize={6} />
<Popover.Content w="max-content" p={4}>
<p>The content of the Popover.</p>
</Popover.Content>
</Popover>
</HStack>
);
}

If the trigger is outside of the popover (like the above example), you may need to disabled closeOnBlur to prevents weird open/close behavior.

Accessibility

ARIA roles and attributes

Popover.Trigger has:

  • aria-haspopup set to dialog to denote that it triggers a popover.
  • aria-controls set to the id of the Popover.Content.
  • aria-expanded synced with the open/closed state of the popover.
  • aria-labelledby set to the id of the Popover.Heading, if any.
  • aria-describedby set to the id of the Popover.Description, if any.

Popover.Content has:

  • role set to dialog, when the triggerMode is hover, role is set to tooltip.

Keyboard support (closed popover)

  • In click mode, clicking the Popover.Trigger or pressing space or enter when focus is on the trigger will open the popover.
  • In hover mode, focusing on or mousing over the Popover.Trigger will open the popover.

Keyboard support (opened popover)

  • If the initialFocusSelector prop is set, focus is moved to the matching element, otherwise focus moves to the Popover.Content.
  • In click mode, clicking the Popover.Trigger closes the popover.
  • In hover mode, blurring or mousing out of the Popover.Trigger will close the popover. If you move your mouse into the Popover.Content, it'll remain visible.
  • When focus is within the Popover.Content and closeOnEsc prop is true, pressing esc closes the popover.
  • When closeOnBur prop is true, clicking outside or blurring out of the Popover.Content closes the popover.
  • When the popover closes, focus returns to the Popover.Trigger unless another focusable element takes focus.

Theming

Use the PopoverTheme type to override Popover styles and default props when extending Hope UI theme.

tsx
import { extendTheme, PopoverTheme } from "@hope-ui/core";
const theme = extendTheme({
components: {
Popover: {
// ...overrides
} as PopoverTheme,
},
});
tsx
import { extendTheme, PopoverTheme } from "@hope-ui/core";
const theme = extendTheme({
components: {
Popover: {
// ...overrides
} as PopoverTheme,
},
});

Component parts

NameStatic selectorDescription
roothope-Popover-rootPopover content
arrowhope-Popover-arrowPopover arrow
headinghope-Popover-headingPopover heading
descriptionhope-Popover-descriptionPopover description

Props

Popover

NameTypeDescriptionDefault value
isOpenbooleanWhether the popover should be shown (in controlled mode).
defaultIsOpenbooleanWhether the popover should be initially shown (in uncontrolled mode).
onOpenChange(isOpen: boolean) => voidA function that will be called when the isOpen state of the popover changes.
idstringThe id of the popover content.
childrenJSX.Element | PopoverChildrenRenderPropChildren of the popover.
transitionOptionsTransitionOptionsOptions passed to the popover transition.
triggerMode"hover" | "click"The interaction that triggers the popover."click"
withArrowbooleanWhether the popover should display an arrow inside it.true
arrowSizenumberThe size of the arrow (in px).24
arrowPaddingnumberThe minimum padding between the arrow and the popover corner (in px).12
placementFloatingPlacementPlacement of the popover."bottom"
hasSameWidthbooleanWhether the popover should have the same width as the trigger/anchor element.
offsetnumberOffset between the popover and the anchor element (in px).12
overflowPaddingnumberThe minimum padding between the popover and the viewport edge (in px).
openDelaynumberDelay before showing the popover on hover (in ms).200
closeDelaynumberDelay before hiding the popover on mouse leave (in ms).200
closeOnBlurbooleanWhether the popover should close when the user blur out it by clicking outside or tabbing out.true
closeOnEscbooleanWhether the popover should close when the user hit the Esc key.true
trapFocusbooleanWhether the focus will be locked into the popover.false
initialFocusSelectorstringA query selector to retrieve the element that should receive focus once the popover opens.
restoreFocusSelectorstringA query selector to retrieve the element that should receive focus once the popover closes.

Other components props

  • Popover.CloseButton renders a CloseButton and supports all of its props.
  • Popover.Trigger , Popover.Anchor, Popover.Content, Popover.Heading and Popover.Description supports commons Hope UI props (style props, sx and as).
Previous
Modal