Popover

Used to show detailed information inside a pop-up

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 fixed to avoid the popover from being clipped by the dialog.
  • Set hideWhenDetached to true to 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

PopoverRootProps
PropDefaultType
autoFocustrue
boolean

Whether 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
closeOnEscapetrue
boolean

Whether Escape closes the popover.

closeOnInteractOutsidetrue
boolean

Whether a click outside the content closes the popover.

defaultOpen—
boolean

The open state a fresh, uncontrolled popover starts in.

defaultTriggerValue—
string | null

The active trigger a fresh, uncontrolled popover starts with.

finalFocusEl—
() => HTMLElement | null

The element that takes focus when the popover closes.

id—
string

Seeds 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—
PopoverElementIds

Override individual element ids, for pointing an ARIA relationship at a specific one.

immediate—
boolean

Apply an open/close change in the same frame rather than the next one.

initialFocusEl—
() => HTMLElement | null

The element that takes focus when the popover opens, instead of the content itself.

lazyMountfalse
boolean

Keep the content out of the DOM entirely until the popover first opens.

modalfalse
boolean

Whether 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) => void

Called when Escape is pressed, before the popover decides whether to close.

onExitComplete—
VoidFunction

Called once the exit animation has finished and the content is fully gone.

onFocusOutside—
(event: PopoverFocusOutsideEvent) => void

Called when focus moves outside the content.

onInteractOutside—
(event: PopoverInteractOutsideEvent) => void

Called on either of the two above.

onOpenChange—
(details: PopoverOpenChangeDetails) => void

Called whenever the open state changes, from either side.

onPointerDownOutside—
(event: PopoverPointerDownOutsideEvent) => void

Called 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) => void

Called whenever the active trigger changes.

open—
boolean

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

portalledtrue
boolean

Whether 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—
PopoverPositioningOptions

Where 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—
boolean

Drive 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`.

restoreFocustrue
boolean

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

skipAnimationOnMountfalse
boolean

Suppress the enter animation on the very first open, so a `defaultOpen` popover does not animate in as the page loads.

translations—
PopoverIntlTranslations

The strings the machine's own controls are labelled with.

triggerValue—
string | null

The controlled active trigger, for one popover shared by several triggers.

unmountOnExitfalse
boolean

Take 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

PopoverRootProviderProps
PropDefaultType
children—
JSX.Element
immediate—
boolean

Apply an open/close change in the same frame rather than the next one.

lazyMountfalse
boolean

Keep the content out of the DOM entirely until the popover first opens.

onExitComplete—
VoidFunction

Called once the exit animation has finished and the content is fully gone.

present—
boolean

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

skipAnimationOnMountfalse
boolean

Suppress the enter animation on the very first open, so a `defaultOpen` popover does not animate in as the page loads.

unmountOnExitfalse
boolean

Take the content back out of the DOM once it has closed and its exit animation has finished, rather than leaving it there hidden.

value*—
CreatePopoverReturn

A 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

PopoverTriggerProps
PropDefaultType
value—
string

Identifies 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

PopoverContextProps
PropDefaultType
children*—
(store: CreatePopoverReturn) => JSX.Element

Receives 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

CreatePopoverProps
PropDefaultType
autoFocustrue
boolean

Whether opening moves focus into the content — to the first focusable thing in it, or to the content itself when there is none.

closeOnEscapetrue
boolean

Whether Escape closes the popover.

closeOnInteractOutsidetrue
boolean

Whether a click outside the content closes the popover.

defaultOpen—
boolean

The open state a fresh, uncontrolled popover starts in.

defaultTriggerValue—
string | null

The active trigger a fresh, uncontrolled popover starts with.

finalFocusEl—
() => HTMLElement | null

The element that takes focus when the popover closes.

id—
string

Seeds 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—
PopoverElementIds

Override individual element ids, for pointing an ARIA relationship at a specific one.

initialFocusEl—
() => HTMLElement | null

The element that takes focus when the popover opens, instead of the content itself.

modalfalse
boolean

Whether 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) => void

Called when Escape is pressed, before the popover decides whether to close.

onFocusOutside—
(event: PopoverFocusOutsideEvent) => void

Called when focus moves outside the content.

onInteractOutside—
(event: PopoverInteractOutsideEvent) => void

Called on either of the two above.

onOpenChange—
(details: PopoverOpenChangeDetails) => void

Called whenever the open state changes, from either side.

onPointerDownOutside—
(event: PopoverPointerDownOutsideEvent) => void

Called 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) => void

Called whenever the active trigger changes.

open—
boolean

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

portalledtrue
boolean

Whether 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—
PopoverPositioningOptions

Where the content is placed and how it reacts to running out of room. The machine merges what you pass over its own `{ placement: "bottom" }`.

restoreFocustrue
boolean

Whether focus returns to whatever had it before the popover opened.

translations—
PopoverIntlTranslations

The strings the machine's own controls are labelled with.

triggerValue—
string | null

The controlled active trigger, for one popover shared by several triggers.

Plus the three every component takes: as, render and unstyled.