<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Popover

A small non-modal panel beside its trigger, for a short form or a few choices: a share link, filters, a quick edit. It is named by its title, closes on Escape and outside presses, returns focus to the trigger, and flips to the other side when there is no room. With `presentation="auto"` it is a bottom sheet on phones (below 640px).

```tsx
import { Popover } from "@/components/ouro/popover/popover";
```

## Example

```tsx
<Popover title="Share recipe" trigger={<Button>Share</Button>}>
  <TextField label="Link" defaultValue={url} readOnly />
</Popover>
<Popover title="Filters" presentation="auto" open={open} onOpenChange={setOpen} anchor={filtersRef}>
  <FilterForm />
</Popover>
```

## Usage

Use `trigger` for a popover a button opens, or `open` with `onOpenChange` and `anchor` to control it. To give the trigger a tooltip too, put the Tooltip inside `trigger`: `trigger={<Tooltip content="Share recipe"><Button aria-label="Share">…</Button></Tooltip>}`.

It stacks above the page at `--ouro-layer`, a z-index of 50 by default (from ouro-base.css), like Dialog and Sheet. Raise it in a `:root` rule of your own after the Ouro tokens, outside any `@layer`: `:root { --ouro-layer: 60; }`.

## Use instead when

- Use `Tooltip` instead for a short label on hover or focus, `HoverCard` for a preview of a link, and `Dialog` when the task needs the user's full attention.

## Props

The props the modes share. Type: `PopoverProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"center"` | How the popover lines up with the trigger along that side. |
| `children` (required) | `ReactNode` |  | The body. It scrolls when the popover is taller than the space beside the trigger. |
| `className` | `string` |  | Layout only, applied to the popover panel: width (18rem, `w-72`, by default) and margins. Your classes win over Ouro UI's. The sheet keeps its full width. Appearance classes warn in development. |
| `closeLabel` | `string` | `"Close"` | The label of the close button, the last control in the popover. It is hidden until it has keyboard focus, so screen-reader users on phones (who have no Escape key) can always close the popover. |
| `description` | `ReactNode` |  | A short line under the title, read out when the popover opens. |
| `hideTitle` | `boolean` |  | Hides the title visually. It still names the popover for screen readers. |
| `presentation` | `"popover" \| "auto" \| "sheet"` | `"popover"` | `popover` stays beside the trigger at every width. `auto` is a popover from the `sm` breakpoint (640px) up and a bottom sheet below it, for a popover with controls that would be fiddly on a phone. `sheet` is always a bottom sheet. The server renders the popover form and corrects it on hydration. |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | The side of the trigger the popover sits on; it flips when there is no room. |
| `title` (required) | `string` |  | Names the popover for screen readers and shows at its top. Always required; hide it with `hideTitle`. |

## Modes

`PopoverProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Controlled: `PopoverControlledProps`

A controlled Popover: you own `open`, update it in `onOpenChange`, and say what it points at with `anchor`.

| Prop | Type | Description |
| --- | --- | --- |
| `anchor` (required) | `RefObject<HTMLElement \| null>` | The element the popover points at, usually the button that toggles it: `anchor={buttonRef}`. A press on it never counts as an outside press, so its own `onClick` can toggle `open`. |
| `onOpenChange` (required) | `(open: boolean) => void` | Called with `false` when the user dismisses the popover (Escape, outside press, the close button). |
| `open` (required) | `boolean` | Whether the popover is open. After a touch or pen tap the popover focuses itself rather than its first input, so no software keyboard pops up; after a key press it focuses the first control. |

Not in this mode (typed `never`):

- `trigger`: Not with `open`. Remove `open`, `onOpenChange` and `anchor` to let a trigger open the popover.

### With trigger: `PopoverWithTriggerProps`

An uncontrolled Popover: `trigger` opens and closes it, and it sits beside the trigger.

| Prop | Type | Description |
| --- | --- | --- |
| `trigger` (required) | `ReactElement` | The element that opens and closes the popover, usually a Button: `trigger={<Button>Share</Button>}`. |

Not in this mode (typed `never`):

- `anchor`: Not with `trigger`. The popover sits beside its trigger.
- `onOpenChange`: Not with `trigger`. Remove `trigger` to control the popover with `open`, `onOpenChange` and `anchor`.
- `open`: Not with `trigger`. Remove `trigger` to control the popover with `open`, `onOpenChange` and `anchor`.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `content`: Not a prop. The body is the children: `<Popover title="…" trigger={…}>…</Popover>`.
- `isOpen`: Not a prop. Ouro UI uses `open` with `onOpenChange`: `<Popover open={open} onOpenChange={setOpen} anchor={buttonRef}>`.
- `onClose`: Not a prop. `onOpenChange(false)` fires on close: `<Popover open={open} onOpenChange={setOpen} anchor={buttonRef}>`.
- `placement`: Not a prop. Ouro UI splits placement into `side` and `align`: `<Popover side="bottom" align="start">`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Popover merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: use the `align` prop.

## Parts

When these props cannot express your layout, build it from the parts in `@/components/ouro/popover/popover-parts`. See [parts.md](parts.md#popover-parts) for the required nesting.

[^layout-classes]: test:registry/ouro/components/ouro/popover/popover.test.tsx::Popover > is 18rem wide by default, and className sets layout verify:testExists(registry/ouro/components/ouro/popover/popover.test.tsx::Popover > is 18rem wide by default, and className sets layout)
