Usage
import { Popover } from "chakra-ui-solid";<Popover.Root>
<Popover.Trigger />
<Popover.Positioner>
<Popover.Content>
<Popover.CloseTrigger />
<Popover.Arrow>
<Popover.ArrowTip />
</Popover.Arrow>
<Popover.Body>
<Popover.Title />
</Popover.Body>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>Shortcuts
The Popover provides a shortcuts for common use cases.
Arrow
The Popover.Arrow renders the Popover.ArrowTip component within in by default.
This works:
<Popover.Arrow>
<Popover.ArrowTip />
</Popover.Arrow>This might be more concise, if you don’t need to customize the arrow tip.
<Popover.Arrow />Examples
Controlled
Use the open and onOpenChange to control the visibility of the popover.
Sizes
Use the size prop to change the size of the popover component.
Lazy Mount
Use the lazyMounted and/or unmountOnExit prop to defer the mounting of the popover content until
it’s opened.
Placement
Use the positioning.placement prop to configure the underlying floating-ui positioning logic.
Offset
Use the positioning.offset prop to adjust the position of the popover content.
Same Width
Use the positioning.sameWidth prop to make the popover content the same width as the trigger.
Nested Popover
When nesting floating elements like popover, select, menu, inside of the popover, avoid portalling them to the document’s body.
-<Portal>
<Popover.Positioner>
<Popover.Content>
{/* ... */}
</Popover.Content>
</Popover.Positioner>
-</Portal>Initial Focus
Use the initialFocusEl prop to set the initial focus of the popover content.
Form
Here’s an example of a popover with a form inside.
Custom Background
Use the --popover-bg CSS variable to change the background color of the popover content and its
arrow.
Open From Dialog
To use the Popover within a Dialog, you need to avoid portalling the Popover.Positioner to the
document’s body.
-<Portal>
<Popover.Positioner>
<Popover.Content>
{/* ... */}
</Popover.Content>
</Popover.Positioner>
-</Portal>If you have set scrollBehavior="inside" on the Dialog, you need to:
- Set the popover positioning to
fixedto avoid the popover from being clipped by the dialog. - Set
hideWhenDetachedtotrueto hide the popover when the trigger is scrolled out of view.
<Popover.Root positioning={{ strategy: "fixed", hideWhenDetached: true }}>{/* ... */}</Popover.Root>Guide
Accessing popover context
Use usePopoverContext to access the popover’s state and methods from any component inside the
popover.
import { Popover, usePopoverContext } from "chakra-ui-solid";
import { Show } from "solid-js";
const PopoverStatus = () => {
const popover = usePopoverContext();
return (
<div>
Popover is{" "}
<Show when={popover.open} fallback="closed">
open
</Show>
</div>
);
};
const MyPopover = () => (
<Popover.Root>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Positioner>
<Popover.Content>
<PopoverStatus />
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
);Popover.Context reaches the same store without a component of your own. Its render prop is called
in the part’s own body, which is not a tracking scope — so it has to return JSX. A callback
returning a bare ternary or a plain string reads the store untracked and freezes on the value it had
at mount.
<Popover.Context>
{(popover) => (
<Popover.Body>
<Show when={popover.open} fallback="closed">
open
</Show>
</Popover.Body>
)}
</Popover.Context>Closing programmatically
Use setOpen(false) from the context to close the popover programmatically.
import { Button, Popover, usePopoverContext } from "chakra-ui-solid";
const CloseButton = () => {
const popover = usePopoverContext();
return <Button onClick={() => popover.setOpen(false)}>Close Popover</Button>;
};
const MyPopover = () => (
<Popover.Root>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Positioner>
<Popover.Content>
<CloseButton />
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
);To drive the popover from outside its tree, build the store yourself with createPopover and hand
it to Popover.RootProvider. Everything the context exposes is on the returned object.
import { Button, createPopover, Popover } from "chakra-ui-solid";
const MyPopover = () => {
const popover = createPopover();
return (
<>
<Button onClick={() => popover.setOpen(false)}>Close Popover</Button>
<Popover.RootProvider value={popover}>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Positioner>
<Popover.Content>
<Popover.Body>Some content</Popover.Body>
</Popover.Content>
</Popover.Positioner>
</Popover.RootProvider>
</>
);
};Positioning based on ref
Use positioning.getAnchorRect() to position the popover based on a custom element ref.
import { Popover } from "chakra-ui-solid";
const MyPopover = () => {
let anchor: HTMLDivElement | undefined;
return (
<>
<div ref={anchor}>Anchor Element</div>
<Popover.Root
positioning={{
getAnchorRect() {
return anchor?.getBoundingClientRect() ?? null;
},
}}
>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Positioner>
<Popover.Content>
<Popover.Body>This popover is anchored to the div above</Popover.Body>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</>
);
};Props
Root
| Prop | Default | Type |
|---|---|---|
autoFocus | true | booleanWhether opening moves focus into the content — to the first focusable thing in it, or to the content itself when there is none. |
children | — | JSX.Element |
closeOnEscape | true | booleanWhether Escape closes the popover. |
closeOnInteractOutside | true | booleanWhether a click outside the content closes the popover. |
defaultOpen | — | booleanThe open state a fresh, uncontrolled popover starts in. |
defaultTriggerValue | — | string | nullThe active trigger a fresh, uncontrolled popover starts with. |
finalFocusEl | — | () => HTMLElement | nullThe element that takes focus when the popover closes. |
id | — | stringSeeds every id the machine hands out — the content is `popover:{id}:content`, the trigger `popover:{id}:trigger`, and so on for all eight parts. Defaults to a generated id. Pass `ids` to name the elements themselves. |
ids | — | PopoverElementIdsOverride individual element ids, for pointing an ARIA relationship at a specific one. |
immediate | — | booleanApply an open/close change in the same frame rather than the next one. |
initialFocusEl | — | () => HTMLElement | nullThe element that takes focus when the popover opens, instead of the content itself. |
lazyMount | false | booleanKeep the content out of the DOM entirely until the popover first opens. |
modal | false | booleanWhether the content blocks the page behind it — pointer events off, scrolling locked, everything else `aria-hidden`, and Tab confined to the content. |
onEscapeKeyDown | — | (event: KeyboardEvent) => voidCalled when Escape is pressed, before the popover decides whether to close. |
onExitComplete | — | VoidFunctionCalled once the exit animation has finished and the content is fully gone. |
onFocusOutside | — | (event: PopoverFocusOutsideEvent) => voidCalled when focus moves outside the content. |
onInteractOutside | — | (event: PopoverInteractOutsideEvent) => voidCalled on either of the two above. |
onOpenChange | — | (details: PopoverOpenChangeDetails) => voidCalled whenever the open state changes, from either side. |
onPointerDownOutside | — | (event: PopoverPointerDownOutsideEvent) => voidCalled on a pointer press outside the content. |
onRequestDismiss | — | popover.Props['onRequestDismiss']Called when a parent layer closing takes this one with it. |
onTriggerValueChange | — | (details: PopoverTriggerValueChangeDetails) => voidCalled whenever the active trigger changes. |
open | — | booleanThe controlled open state. Pass `undefined` for uncontrolled. |
persistentElements | — | Array<() => Element | null>Elements that keep their pointer events and do not count as "outside" — a toast, a tour step. |
portalled | true | booleanWhether Tab moves out of the content into whatever follows the **trigger**, rather than whatever follows the content in the DOM. Leave it on when the content is inside a `<Portal>`, which is the usual arrangement. |
positioning | — | PopoverPositioningOptionsWhere the content is placed and how it reacts to running out of room. The machine merges what you pass over its own `{ placement: "bottom" }`. |
present | — | booleanDrive the content's presence from something other than the popover's own `open` state — an escape hatch for animating the surface independently of the machine. Resolved with `??`, so a wrapper forwarding an unset `present={props.present}` falls back to `open`. |
restoreFocus | true | booleanWhether focus returns to whatever had it before the popover opened. |
size | 'md' | ConditionalValue<'xs' | 'sm' | 'md' | 'lg' | PresetVariant<'popover', 'size'>>How wide the content is and how much padding it carries. |
skipAnimationOnMount | false | booleanSuppress the enter animation on the very first open, so a `defaultOpen` popover does not animate in as the page loads. |
translations | — | PopoverIntlTranslationsThe strings the machine's own controls are labelled with. |
triggerValue | — | string | nullThe controlled active trigger, for one popover shared by several triggers. |
unmountOnExit | false | booleanTake the content back out of the DOM once it has closed and its exit animation has finished, rather than leaving it there hidden. |
Plus the three every component takes: as, render and unstyled.
RootProvider
| Prop | Default | Type |
|---|---|---|
children | — | JSX.Element |
immediate | — | booleanApply an open/close change in the same frame rather than the next one. |
lazyMount | false | booleanKeep the content out of the DOM entirely until the popover first opens. |
onExitComplete | — | VoidFunctionCalled once the exit animation has finished and the content is fully gone. |
present | — | booleanDrive the content's presence from something other than the popover's own `open` state — an escape hatch for animating the surface independently of the machine. Resolved with `??`, so a wrapper forwarding an unset `present={props.present}` falls back to `open`. |
size | 'md' | ConditionalValue<'xs' | 'sm' | 'md' | 'lg' | PresetVariant<'popover', 'size'>>How wide the content is and how much padding it carries. |
skipAnimationOnMount | false | booleanSuppress the enter animation on the very first open, so a `defaultOpen` popover does not animate in as the page loads. |
unmountOnExit | false | booleanTake the content back out of the DOM once it has closed and its exit animation has finished, rather than leaving it there hidden. |
value* | — | CreatePopoverReturnA machine built by {@link createPopover}, so the consumer owns it rather than the Root. |
Plus the three every component takes: as, render and unstyled.
Trigger
| Prop | Default | Type |
|---|---|---|
value | — | stringIdentifies this trigger among several driving one popover — it becomes the machine's `triggerValue`, and `data-current` marks the one that opened it. It shadows the `button`'s own `value` attribute, which is Ark's split too: this is a machine argument and never reaches the DOM. |
Plus Omit<HTMLChakraProps<"button">, "value"> — the whole style-prop surface and the DOM attributes of the element it renders, several hundred names listed as their sources rather than expanded — and the three every component takes: as, render and unstyled.
Context
| Prop | Default | Type |
|---|---|---|
children* | — | (store: CreatePopoverReturn) => JSX.ElementReceives the machine, so a consumer can read its state without a component of their own. |
Plus the three every component takes: as, render and unstyled.
createPopover
| Prop | Default | Type |
|---|---|---|
autoFocus | true | booleanWhether opening moves focus into the content — to the first focusable thing in it, or to the content itself when there is none. |
closeOnEscape | true | booleanWhether Escape closes the popover. |
closeOnInteractOutside | true | booleanWhether a click outside the content closes the popover. |
defaultOpen | — | booleanThe open state a fresh, uncontrolled popover starts in. |
defaultTriggerValue | — | string | nullThe active trigger a fresh, uncontrolled popover starts with. |
finalFocusEl | — | () => HTMLElement | nullThe element that takes focus when the popover closes. |
id | — | stringSeeds every id the machine hands out — the content is `popover:{id}:content`, the trigger `popover:{id}:trigger`, and so on for all eight parts. Defaults to a generated id. Pass `ids` to name the elements themselves. |
ids | — | PopoverElementIdsOverride individual element ids, for pointing an ARIA relationship at a specific one. |
initialFocusEl | — | () => HTMLElement | nullThe element that takes focus when the popover opens, instead of the content itself. |
modal | false | booleanWhether the content blocks the page behind it — pointer events off, scrolling locked, everything else `aria-hidden`, and Tab confined to the content. |
onEscapeKeyDown | — | (event: KeyboardEvent) => voidCalled when Escape is pressed, before the popover decides whether to close. |
onFocusOutside | — | (event: PopoverFocusOutsideEvent) => voidCalled when focus moves outside the content. |
onInteractOutside | — | (event: PopoverInteractOutsideEvent) => voidCalled on either of the two above. |
onOpenChange | — | (details: PopoverOpenChangeDetails) => voidCalled whenever the open state changes, from either side. |
onPointerDownOutside | — | (event: PopoverPointerDownOutsideEvent) => voidCalled on a pointer press outside the content. |
onRequestDismiss | — | popover.Props['onRequestDismiss']Called when a parent layer closing takes this one with it. |
onTriggerValueChange | — | (details: PopoverTriggerValueChangeDetails) => voidCalled whenever the active trigger changes. |
open | — | booleanThe controlled open state. Pass `undefined` for uncontrolled. |
persistentElements | — | Array<() => Element | null>Elements that keep their pointer events and do not count as "outside" — a toast, a tour step. |
portalled | true | booleanWhether Tab moves out of the content into whatever follows the **trigger**, rather than whatever follows the content in the DOM. Leave it on when the content is inside a `<Portal>`, which is the usual arrangement. |
positioning | — | PopoverPositioningOptionsWhere the content is placed and how it reacts to running out of room. The machine merges what you pass over its own `{ placement: "bottom" }`. |
restoreFocus | true | booleanWhether focus returns to whatever had it before the popover opened. |
translations | — | PopoverIntlTranslationsThe strings the machine's own controls are labelled with. |
triggerValue | — | string | nullThe controlled active trigger, for one popover shared by several triggers. |
Plus the three every component takes: as, render and unstyled.