# Popover

Rich content in a floating panel anchored to a trigger.

Source: [popover.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/popover.tsx)

```tsx
import { CopyIcon, ShareIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
import { toast } from "@/components/ui/toast";

export default function PopoverBasic() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="secondary" />}>
        <ShareIcon />
        Share
      </PopoverTrigger>
      <PopoverContent align="start">
        <PopoverHeader>
          <PopoverTitle>Share “Mobile app”</PopoverTitle>
          <PopoverDescription>Anyone with the link can view this project.</PopoverDescription>
        </PopoverHeader>
        <div className="flex gap-2">
          <Input readOnly defaultValue="https://example.com/p/mobile-app" aria-label="Share link" />
          <Button
            variant="secondary"
            size="icon"
            aria-label="Copy link"
            onClick={() => toast.add({ title: "Link copied" })}
          >
            <CopyIcon />
          </Button>
        </div>
      </PopoverContent>
    </Popover>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/popover
```

## Usage

```tsx
import { Button } from "@/components/ui/button";
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";

export function DetailsPopover() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="secondary" />}>Details</PopoverTrigger>
      <PopoverContent>
        <PopoverHeader>
          <PopoverTitle>Website redesign</PopoverTitle>
          <PopoverDescription>18 of 28 tasks done.</PopoverDescription>
        </PopoverHeader>
      </PopoverContent>
    </Popover>
  );
}
```

Unlike a [Dialog](https://ui.jarv.is/components/dialog.md), a popover leaves the rest of the page interactive. `side` and
`align` on `PopoverContent` set its position.

## Composition

- `Popover`
  - `PopoverTrigger`
  - `PopoverContent`
    - `PopoverHeader`
      - `PopoverTitle`
      - `PopoverDescription`

## Examples

### Form controls

Set a `w-*` class on `PopoverContent` to fit its content.

```tsx
import { ListFilterIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Field, FieldItem, FieldLabel, FieldSeparator } from "@/components/ui/field";
import {
  Popover,
  PopoverContent,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";

const filters = [
  { value: "all", label: "All tasks" },
  { value: "mine", label: "Assigned to me" },
  { value: "unassigned", label: "Unassigned" },
];

export default function PopoverFormControls() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="secondary" />}>
        <ListFilterIcon />
        Filter
      </PopoverTrigger>
      <PopoverContent align="start" className="w-64">
        <PopoverHeader>
          <PopoverTitle>Filter tasks</PopoverTitle>
        </PopoverHeader>
        <RadioGroup defaultValue="all" aria-label="Tasks">
          {filters.map(({ value, label }) => (
            <FieldItem key={value}>
              <RadioGroupItem value={value} />
              <FieldLabel>{label}</FieldLabel>
            </FieldItem>
          ))}
        </RadioGroup>
        <FieldSeparator />
        <Field orientation="horizontal">
          <Checkbox />
          <FieldLabel>Include completed</FieldLabel>
        </Field>
      </PopoverContent>
    </Popover>
  );
}
```

### Infotip

`openOnHover` on `PopoverTrigger` opens the popover on hover as well as click. Unlike a
[Tooltip](https://ui.jarv.is/components/tooltip.md), it works on touch screens and with screen readers, and can hold a link.
Give the icon button an `aria-label`.

```tsx
import { InfoIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverTrigger,
} from "@/components/ui/popover";

export default function PopoverInfotip() {
  return (
    <Field className="w-64">
      <div className="flex items-center gap-1">
        <FieldLabel htmlFor="rate-limit">Rate limit</FieldLabel>
        <Popover>
          <PopoverTrigger
            openOnHover
            render={
              <Button
                variant="ghost"
                size="icon"
                aria-label="About rate limits"
                className="size-5 text-muted-foreground"
              />
            }
          >
            <InfoIcon className="size-3.5" />
          </PopoverTrigger>
          <PopoverContent side="top" className="w-60 p-2.5">
            <PopoverDescription className="text-xs">
              Requests each API key can make per minute. Requests over the limit get a 429 response.{" "}
              <a
                href="#rate-limits"
                className="font-medium text-foreground underline decoration-border-strong decoration-[0.5px] underline-offset-2"
              >
                Learn more
              </a>
            </PopoverDescription>
          </PopoverContent>
        </Popover>
      </div>
      <Input id="rate-limit" type="number" defaultValue={600} />
    </Field>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `Popover` | [`Popover.Root`](https://base-ui.com/react/components/popover#root) |
| `PopoverTrigger` | [`Popover.Trigger`](https://base-ui.com/react/components/popover#trigger) |
| `PopoverContent` | [`Popover.Popup`](https://base-ui.com/react/components/popover#popup) |
| `PopoverHeader` | `<div>` |
| `PopoverTitle` | [`Popover.Title`](https://base-ui.com/react/components/popover#title) |
| `PopoverDescription` | [`Popover.Description`](https://base-ui.com/react/components/popover#description) |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### Popover

| Prop                    | Type                                                                       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ----------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `defaultOpen?`          | `boolean`                                                                  | Whether the popover is initially open. To render a controlled popover, use the `open` prop instead. Default: `false`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `open?`                 | `boolean`                                                                  | Whether the popover is currently open.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `onOpenChange?`         | `((open: boolean, eventDetails: Popover.Root.ChangeEventDetails) => void)` | Event handler called when the popover is opened or closed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `onOpenChangeComplete?` | `((open: boolean) => void)`                                                | Event handler called after any animations complete when the popover is opened or closed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `actionsRef?`           | `React.RefObject<PopoverRootActions \| null>`                              | A ref to imperative actions. - `unmount`: Ends the closing phase of the popover after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the popover completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the popover imperatively when called.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `modal?`                | `"trap-focus" \| boolean`                                                  | 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. Default: `false` |
| `triggerId?`            | `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).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `defaultTriggerId?`     | `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.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `handle?`               | `Popover.Handle<unknown>`                                                  | A handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `children?`             | `PayloadChildRenderFunction<unknown> \| React.ReactNode`                   | The content of the popover. This can be a regular React node or a render function that receives the `payload` of the active trigger.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

### PopoverTrigger

| Prop            | Type                      | Description                                                                                                                                                                                                                                                                                                                                                                 |
| --------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nativeButton?` | `boolean`                 | 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>`). 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>`). Default: `true` |
| `id?`           | `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).                                                                                                                                                                        |
| `handle?`       | `Popover.Handle<unknown>` | A handle to associate the trigger with a popover.                                                                                                                                                                                                                                                                                                                           |
| `payload?`      | `unknown`                 | A payload to pass to the popover when it is opened.                                                                                                                                                                                                                                                                                                                         |
| `openOnHover?`  | `boolean`                 | Whether the popover should also open when the trigger is hovered. Default: `false`                                                                                                                                                                                                                                                                                          |
| `delay?`        | `number`                  | How long to wait before the popover may be opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. Default: `300`                                                                                                                                                                                                                                      |
| `closeDelay?`   | `number`                  | How long to wait before closing the popover that was opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. Default: `0`                                                                                                                                                                                                                              |

### PopoverContent

| Prop            | Type                                                                                                                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `initialFocus?` | `boolean \| React.RefObject<HTMLElement \| null> \| ((openType: InteractionType) => void \| boolean \| 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. |
| `finalFocus?`   | `boolean \| React.RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => void \| boolean \| 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.                                                                                                                                                                                        |
| `anchor?`       | `Element \| React.RefObject<Element \| null> \| VirtualElement \| (() => Element \| VirtualElement \| null) \| null`          | An element to position the popup against. By default, the popup will be positioned against the trigger.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `side?`         | `"bottom" \| "inline-end" \| "inline-start" \| "left" \| "right" \| "top"`                                                    | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. Default: `"bottom"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `sideOffset?`   | `number \| OffsetFunction`                                                                                                    | Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. Default: `4`      |
| `align?`        | `"center" \| "end" \| "start"`                                                                                                | How to align the popup relative to the specified side. Default: `"center"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `alignOffset?`  | `number \| OffsetFunction`                                                                                                    | Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. Default: `0`       |
