> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Popover

> Display contextual content anchored to a trigger.

export const StoryEmbed = ({storyId, title, height = 240}) => <>
    <Tabs>
      <Tab title="Light">
        <iframe title={`${title} (light)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:light`} width="100%" height={height} loading="lazy" style={{
  border: 0
}} />
      </Tab>
      <Tab title="Dark">
        <iframe title={`${title} (dark)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:dark`} width="100%" height={height} loading="lazy" style={{
  border: 0
}} />
      </Tab>
    </Tabs>
    <a href={`https://storybook.twenty.com/?path=/story/${storyId}`}>
      Open in Storybook
    </a>
  </>;

Use `Popover` for contextual information or lightweight controls. `Popup` includes positioning and a portal, so you can compose content directly inside it.

<StoryEmbed storyId="ui-surfaces-popover--documentation" title="Popover example" height={440} />

## Anatomy

Keep the trigger and popup inside `Popover.Root`. The popup can contain text, form controls, and actions.

```text theme={null}
Popover.Root
├── Popover.Trigger
└── Popover.Popup
    ├── Popover.Title
    ├── Popover.Description
    └── Popover.Close
```

| Part          | Requirement                             | Purpose                                                               |
| ------------- | --------------------------------------- | --------------------------------------------------------------------- |
| `Root`        | Required                                | Coordinates open state and interactions.                              |
| `Trigger`     | Required for a trigger-anchored popover | Opens or closes the popup and supplies its anchor.                    |
| `Popup`       | Required                                | Renders positioned content with focus management.                     |
| `Title`       | Recommended                             | Supplies a visible popup name. An accessible name is always required. |
| `Description` | Optional                                | Associates explanatory text with the popup.                           |
| `Close`       | Optional                                | Provides an explicit close action within the popup.                   |

`Popup` includes the portal and positioner. Set `arrow` on `Popup` to add its built-in arrow. All parts share their root's context even though the popup renders in a portal; the tree describes component nesting, not the final DOM placement.

## Uncontrolled state

Use `defaultOpen` to set the initial visibility. The popover owns subsequent changes from its trigger, close controls, and dismissal interactions.

```tsx theme={null}
import { Popover } from 'twenty-ui/primitives/surfaces';

export const ImportHelp = () => (
  <Popover.Root defaultOpen={false}>
    <Popover.Trigger>Import help</Popover.Trigger>
    <Popover.Popup arrow>
      <Popover.Title>Import a CSV file</Popover.Title>
      <Popover.Description>
        Use a header row to identify your columns.
      </Popover.Description>
      <Popover.Close>Got it</Popover.Close>
    </Popover.Popup>
  </Popover.Root>
);
```

`defaultOpen` defaults to `false`, so it can be omitted. Use `Title` and `Description` to identify the popup. The trigger renders a button by default; `render` lets you supply a compatible element.

## Controlled state

Pass `open` and update it with `onOpenChange` to own the same popover's visibility in React state.

```tsx theme={null}
import { useState } from 'react';
import { Popover } from 'twenty-ui/primitives/surfaces';

export const ImportHelp = () => {
  const [open, setOpen] = useState(false);

  return (
    <Popover.Root open={open} onOpenChange={setOpen}>
      <Popover.Trigger>Import help</Popover.Trigger>
      <Popover.Popup arrow>
        <Popover.Title>Import a CSV file</Popover.Title>
        <Popover.Description>
          Use a header row to identify your columns.
        </Popover.Description>
        <Popover.Close>Got it</Popover.Close>
      </Popover.Popup>
    </Popover.Root>
  );
};
```

`onOpenChange` receives the next boolean and event details, including changes requested by the trigger, close controls, Escape, and outside interaction.

## Compose with buttons

Pass a Twenty UI `Button` through `render` to use it as the trigger or close control. The popover keeps its open state, keyboard behavior, and focus handling.

```tsx theme={null}
import { Button } from 'twenty-ui/primitives/input';
import { Popover } from 'twenty-ui/primitives/surfaces';

export const ImportHelp = () => (
  <Popover.Root>
    <Popover.Trigger
      render={<Button title="Import help" variant="secondary" />}
    />
    <Popover.Popup arrow>
      <Popover.Title>Import a CSV file</Popover.Title>
      <Popover.Description>
        Use a header row to identify your columns.
      </Popover.Description>
      <Popover.Close render={<Button title="Got it" />} />
    </Popover.Popup>
  </Popover.Root>
);
```

`render` composes behavior onto the supplied button. Custom wrapper components must [forward the supplied props and ref](/ui/primitives/overview#custom-components) to preserve that behavior.

## Placement and focus

`Popup` defaults to `side="bottom"`, `align="center"`, and `sideOffset={8}`. Set `arrow` to show an arrow. Set `container` to override the theme's portal container; see [theming](/ui/theming).

Escape closes the popup and focus returns to the trigger. Outside interaction dismisses it by default. Use the focus props on `Popup` when its content needs a specific initial or return focus target.

## Props

The reference is generated from the public component types. Native attributes, including accessible names and event handlers, are also accepted on parts that render elements.

### Popover.Root

<ParamField body="Root.actionsRef" type="RefObject<PopoverRootActions | null>">
  A ref to imperative actions.

  * `unmount`: Manually unmounts the popover.
    Call this after any externally controlled closing animation finishes.
  * `close`: Closes the popover imperatively when called.
</ParamField>

<ParamField body="Root.children" type="ReactNode | PayloadChildRenderFunction<Payload>">
  The content of the popover.
  This can be a regular React node or a render function that receives the `payload` of the active trigger.
</ParamField>

<ParamField body="Root.defaultOpen" type="boolean" default="false">
  Whether the popover is initially open.

  To render a controlled popover, use the `open` prop instead.
</ParamField>

<ParamField body="Root.defaultTriggerId" type="string | null">
  ID of the trigger that the popover is associated with.
  This is useful in conjunction with the `defaultOpen` prop to create an initially open popover.
</ParamField>

<ParamField body="Root.handle" type="PopoverHandle<Payload>">
  A handle to associate the popover with a trigger.
  If specified, allows external triggers to control the popover's open state.
</ParamField>

<ParamField body="Root.modal" type="boolean | &#x22;trap-focus&#x22;" default="false">
  Determines if the popover enters a modal state when open.

  * `true`: user interaction is limited to the popover: document page scroll is locked, and pointer interactions on outside elements are disabled.
  * `false`: user interaction with the rest of the document is allowed.
  * `'trap-focus'`: focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled.

  On touch devices, a `true` modal blocks outside taps but leaves the page scrollable unless the popup spans nearly the full viewport width, matching native iOS behavior.

  When `modal` is `true`, focus trapping is enabled only if `<Popover.Close>` is rendered
  inside `<Popover.Popup>`. It can be visually hidden with your own CSS if needed, such as
  Tailwind's `sr-only` utility.

  When `modal` is `'trap-focus'`, render `<Popover.Close>` inside `<Popover.Popup>` so touch
  screen readers can escape the popup.
</ParamField>

<ParamField body="Root.onOpenChange" type="((open: boolean, eventDetails: PopoverRootChangeEventDetails) => void)">
  Event handler called when the popover is opened or closed.
</ParamField>

<ParamField body="Root.onOpenChangeComplete" type="((open: boolean) => void)">
  Event handler called after any animations complete when the popover is opened or closed.
</ParamField>

<ParamField body="Root.open" type="boolean">
  Whether the popover is currently open.
</ParamField>

<ParamField body="Root.triggerId" type="string | null">
  ID of the trigger that the popover is associated with.
  This is useful in conjunction with the `open` prop to create a controlled popover.
  There's no need to specify this prop when the popover is uncontrolled (that is, when the `open` prop is not set).
</ParamField>

### Popover.Trigger

<ParamField body="Trigger.className" type="string | ((state: PopoverTriggerState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Trigger.closeDelay" type="number" default="0">
  How long to wait before closing the popover that was opened on hover.
  Specified in milliseconds.

  Requires the `openOnHover` prop.
</ParamField>

<ParamField body="Trigger.delay" type="number" default="300">
  How long to wait before the popover may be opened on hover. Specified in milliseconds.

  Requires the `openOnHover` prop.
</ParamField>

<ParamField body="Trigger.handle" type="PopoverHandle<Payload>">
  A handle to associate the trigger with a popover.
</ParamField>

<ParamField body="Trigger.id" type="string">
  ID of the trigger. In addition to being forwarded to the rendered element,
  it is also used to specify the active trigger for the popover in controlled mode (with the PopoverRoot `triggerId` prop).
</ParamField>

<ParamField body="Trigger.nativeButton" type="boolean" default="true">
  Whether the component renders a native `<button>` element when replacing it
  via the `render` prop.
  Set to `false` if the rendered element is not a button (e.g. `<div>`).
</ParamField>

<ParamField body="Trigger.openOnHover" type="boolean" default="false">
  Whether the popover should also open when the trigger is hovered.
</ParamField>

<ParamField body="Trigger.payload" type="Payload">
  A payload to pass to the popover when it is opened.
</ParamField>

<ParamField body="Trigger.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Trigger.style" type="CSSProperties | ((state: PopoverTriggerState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

### Popover.Popup

<ParamField body="Popup.align" type="&#x22;center&#x22; | &#x22;start&#x22; | &#x22;end&#x22;" default="center">
  Alignment of the popup along the anchor.
</ParamField>

<ParamField body="Popup.alignOffset" type="number">
  Offset in pixels along the alignment axis.
</ParamField>

<ParamField body="Popup.anchor" type="Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null">
  Element or position the popup is anchored to. Defaults to the trigger.
</ParamField>

<ParamField body="Popup.arrow" type="boolean" default="false">
  Shows an arrow pointing at the anchor.
</ParamField>

<ParamField body="Popup.className" type="string | ((state: PopoverPopupState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Popup.container" type="HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null">
  Element the popup is portaled into. Defaults to the theme's portal
  container.
</ParamField>

<ParamField body="Popup.finalFocus" type="boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)">
  Determines the element to focus when the popover is closed.

  * `false`: Do not move focus.
  * `true`: Move focus based on the default behavior (trigger or previously focused element).
  * `RefObject`: Move focus to the ref element.
  * `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).
    Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing.
</ParamField>

<ParamField body="Popup.initialFocus" type="boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)">
  Determines the element to focus when the popover is opened.
  By default, focus moves to the first tabbable element inside the popup, except when the popover
  is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.

  * `false`: Do not move focus.
  * `true`: Move focus based on the default behavior (first tabbable element or popup).
  * `RefObject`: Move focus to the ref element.
  * `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).
    Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing.
</ParamField>

<ParamField body="Popup.keepMounted" type="boolean">
  Keeps the popup mounted in the DOM while the popover is closed.
</ParamField>

<ParamField body="Popup.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverPopupState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Popup.side" type="&#x22;top&#x22; | &#x22;bottom&#x22; | &#x22;left&#x22; | &#x22;right&#x22; | &#x22;inline-end&#x22; | &#x22;inline-start&#x22;" default="bottom">
  Side of the anchor the popup is placed on.
</ParamField>

<ParamField body="Popup.sideOffset" type="number" default="8">
  Distance in pixels between the anchor and the popup.
</ParamField>

<ParamField body="Popup.style" type="CSSProperties | ((state: PopoverPopupState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

### Popover.Title

<ParamField body="Title.className" type="string | ((state: PopoverTitleState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Title.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTitleState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Title.style" type="CSSProperties | ((state: PopoverTitleState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

### Popover.Description

<ParamField body="Description.className" type="string | ((state: PopoverDescriptionState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Description.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverDescriptionState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Description.style" type="CSSProperties | ((state: PopoverDescriptionState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

### Popover.Close

<ParamField body="Close.className" type="string | ((state: PopoverCloseState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</ParamField>

<ParamField body="Close.nativeButton" type="boolean" default="true">
  Whether the component renders a native `<button>` element when replacing it
  via the `render` prop.
  Set to `false` if the rendered element is not a button (for example, `<div>`).
</ParamField>

<ParamField body="Close.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverCloseState>">
  Allows you to replace the component's HTML element
  with a different tag, or compose it with another component.

  Accepts a `ReactElement` or a function that returns the element to render.
</ParamField>

<ParamField body="Close.style" type="CSSProperties | ((state: PopoverCloseState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>
