# Popover

> A small panel that opens from a button, with an arrow that points back at it, for extra details or a quick form.

- Name: `popover`
- Status: beta
- Categories: overlays
- File: the install writes `popover.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Gallery: https://arbokui.com/components/popover
- Registry item: https://arbokui.com/r/popover.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

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

## Use it for

- Extra details people ask for, like shipping terms or who edited a file last.
- A quick form that belongs to one button, like renaming a file or picking a date range.
- Help text behind an info button. Touch screens and screen readers can open a popover, unlike a tooltip.
- Give it a PopoverTitle. It names the popover for screen readers.
- The panel opens at the end of the page body. In a box with its own theme, like a dark preview on a light page, pass that box as container on PopoverPopup so the panel keeps its colors. A container that clips its overflow can clip the panel.

## Avoid it for

- A label for an icon button. Use a tooltip.
- A list of actions. Use a menu, which has arrow-key movement.
- A task that must be finished or cancelled before going on. Use a dialog, which blocks the page.
- Long content or several steps. Use a dialog or a page.

## Props

- `Popover`: `{ open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean, details) => void; modal?: boolean | "trap-focus" }`. Root. Holds one popover's open state and renders no element. Takes open, defaultOpen, onOpenChange, and modal.
- `open`: `boolean`. Popover. Whether it is open, controlled. Use it with onOpenChange.
- `defaultOpen`: `boolean`. Default `false`. Popover. Whether it is open at the start, when you do not control it.
- `onOpenChange`: `(open: boolean, details) => void`. Popover. Called with the new open state when it opens or closes.
- `modal`: `boolean | "trap-focus"`. Default `false`. Popover. true blocks the page and its scroll while it is open. "trap-focus" keeps focus inside and leaves the page free. Either way, put a PopoverClose inside, so touch screen readers can leave it.
- `PopoverTrigger`: `ComponentProps<"button"> & { render?: ReactElement; openOnHover?: boolean }`. Part. The button that opens it. Pass render={\<Button />} to use your own button.
- `PopoverPopup`: `ComponentProps<"div">`. Part. The panel. It renders in a portal, above the page, while open.
- `PopoverTitle`: `ComponentProps<"h2">`. Part. The popover's name, in semibold. An \<h2> by default. Use render={\<h3 />} to fit your headings.
- `PopoverDescription`: `ComponentProps<"p">`. Part. Muted text under the title. Screen readers read it when the popover opens.
- `PopoverClose`: `ComponentProps<"button"> & { render?: ReactElement }`. Part. A button that closes the popover.
- `side`: `"top" | "right" | "bottom" | "left"`. Default `"bottom"`. PopoverPopup. Which side of the trigger it opens on. It flips when there is no room.
- `align`: `"start" | "center" | "end"`. Default `"center"`. PopoverPopup. How it lines up with the trigger.
- `sideOffset`: `number`. Default `12, or 6 with no arrow`. PopoverPopup. Gap between the trigger and the panel, in pixels. The arrow sits in this gap.
- `collisionPadding`: `number | { top?: number; right?: number; bottom?: number; left?: number }`. Default `8`. PopoverPopup. Least gap between the panel and the screen edge, in pixels. It keeps the panel off the edge on phones.
- `arrow`: `boolean`. Default `true`. PopoverPopup. Shows a small arrow that points at the trigger.
- `container`: `HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null`. PopoverPopup. The element the panel opens in. By default, the end of the page body, so no parent can clip it. Pass an element, or a ref itself (not ref.current), to open it inside, for example to keep a parent's theme. Only client code has elements and refs. A null container shows no panel.
- `effect`: `"rise" | "grow"`. Default `"rise"`. PopoverPopup. How the panel opens and closes. rise fades in and moves out from the trigger. grow fades in and grows from the trigger, and closes the same way. Left out, it follows an effect-\<name> class on \<body>, where popups open, and rises with none.
- `className`: `string`. PopoverPopup. Extra classes. Later classes win, so className='w-96' replaces the width.

## Accessibility

- The panel has role="dialog". PopoverTitle names it, and PopoverDescription describes it.
- The trigger has aria-expanded, and aria-haspopup="dialog" tells screen readers what it opens.
- Opening it moves focus to the first control inside. With no control, or when opened by touch, focus goes to the panel itself.
- Escape closes it, and so does a click outside. Closing it puts focus back on the trigger.
- Focus is not trapped. Tab past the last control closes it and moves on to the next control after the trigger.
- To keep focus inside, set modal="trap-focus" on Popover. Keep a PopoverClose inside too, so touch screen reader users can get out.
- The arrow is only a picture. Screen readers skip it.
- The theme tests hold popover text and muted text to 4.5:1 on the popover color, and focus rings and input borders to 3:1, in light and dark.

## Examples

### Details behind a button (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Popover>
  <PopoverTrigger render={<Button emphasis="outline" />}>Shipping</PopoverTrigger>
  <PopoverPopup>
    <PopoverTitle>Free shipping</PopoverTitle>
    <PopoverDescription>Orders over $50 ship free in the US.</PopoverDescription>
  </PopoverPopup>
</Popover>
```

### A quick form (needs @arbokui/button, input, and label)

```tsx
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

function RenameFile({ name, rename }: { name: string; rename: (data: FormData) => void }) {
  return (
    <Popover>
      <PopoverTrigger render={<Button emphasis="outline" />}>Rename</PopoverTrigger>
      <PopoverPopup>
        <PopoverTitle>Rename file</PopoverTitle>
        <form action={rename} className="mt-3 flex flex-col gap-3">
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="file-name">Name</Label>
            <Input id="file-name" name="name" size="sm" defaultValue={name} />
          </div>
          <div className="flex justify-end gap-2">
            <PopoverClose render={<Button size="sm" emphasis="ghost" />}>Cancel</PopoverClose>
            <Button type="submit" size="sm" tone="brand">
              Save
            </Button>
          </div>
        </form>
      </PopoverPopup>
    </Popover>
  );
}
```

### Above the trigger, with no arrow (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Popover>
  <PopoverTrigger render={<Button emphasis="ghost" />}>Details</PopoverTrigger>
  <PopoverPopup side="top" arrow={false}>
    <PopoverTitle>Last edited</PopoverTitle>
    <PopoverDescription>Today at 9:41 by Sam Lee.</PopoverDescription>
  </PopoverPopup>
</Popover>
```

### Grows from its trigger (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<Popover>
  <PopoverTrigger render={<Button emphasis="outline" />}>Details</PopoverTrigger>
  <PopoverPopup effect="grow">
    <PopoverTitle>Last edited</PopoverTitle>
    <PopoverDescription>Today at 9:41.</PopoverDescription>
  </PopoverPopup>
</Popover>
```
