```tsx
import * as Popover from '@gentleduck/primitives/popover'
```

## Anatomy

```tsx
<Popover.Root>
  <Popover.Trigger />
  <Popover.Anchor />      {/* Optional custom anchor point */}
  <Popover.Portal>
    <Popover.Content>
      <Popover.Arrow />    {/* Optional */}
      <Popover.Close />    {/* Optional */}
    </Popover.Content>
  </Popover.Portal>
</Popover.Root>
```

***

## Example

```tsx
import * as Popover from '@gentleduck/primitives/popover'

function UserMenu() {
  return (
    <Popover.Root>
      <Popover.Trigger className="px-3 py-1 border rounded">
        Settings
      </Popover.Trigger>

      <Popover.Portal>
        <Popover.Content
          className="bg-white shadow-lg rounded-lg p-4 w-64 border"
          sideOffset={5}
        >
          <p className="font-medium mb-2">User settings</p>
          <input placeholder="Display name" className="w-full border rounded px-2 py-1 mb-2" />
          <Popover.Close className="text-sm text-blue-600">Done</Popover.Close>
          <Popover.Arrow className="fill-white" />
        </Popover.Content>
      </Popover.Portal>
    </Popover.Root>
  )
}
```

***

## API

### `Popover.Root`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | - | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state |
| `onOpenChange` | `(open: boolean) => void` | - | Called on state change |
| `modal` | `boolean` | `false` | Enable modal behavior (focus trap, scroll lock) |
| `dir` | `'ltr' \| 'rtl'` | - | Reading direction for keyboard navigation |

### `Popover.Trigger`

Toggles the popover. Sets `aria-expanded` and `aria-controls` automatically.

### `Popover.Anchor`

}>
  Optional custom anchor point. By default, content is positioned relative to the Trigger. Use Anchor to position relative to a different element.

### `Popover.Portal`

Portals content to `document.body`.

### `Popover.Content`

The floating content. Positioned by the Popper engine relative to the trigger.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | Preferred side |
| `sideOffset` | `number` | `0` | Distance from anchor in pixels |
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alignment along the side |
| `alignOffset` | `number` | `0` | Alignment offset in pixels |
| `forceMount` | `true` | - | Keep mounted always |
| `onOpenAutoFocus` | `(event) => void` | - | Intercept auto-focus |
| `onCloseAutoFocus` | `(event) => void` | - | Intercept focus restoration |
| `onPointerDownOutside` | `(event) => void` | - | Called on outside click |
| `onFocusOutside` | `(event) => void` | - | Called when focus moves outside |
| `onInteractOutside` | `(event) => void` | - | Called for any outside interaction |
| `onEscapeKeyDown` | `(event) => void` | - | Called on <Kbd>Escape</Kbd> press |
| `trapFocus` | `boolean` | `context.open` | Override whether focus is trapped inside the content |
| `disableOutsidePointerEvents` | `boolean` | `context.open` | Override whether pointer events outside are blocked |
| `lockScroll` | `boolean` | `context.open` | Override whether body scroll is locked while open |

The `trapFocus`, `disableOutsidePointerEvents`, and `lockScroll` props default to the popover's open state. Override them when integrating with animation libraries like [motion](https://motion.dev) that need custom lifecycle control during exit animations.

### `Popover.Arrow`

Optional visual arrow pointing toward the anchor.

### `Popover.Close`

Button that closes the popover.

***

## Keyboard interactions

| Key | Action |
| --- | --- |
| <Kbd>Space</Kbd> / <Kbd>Enter</Kbd> | Toggle popover (on Trigger) |
| <Kbd>Escape</Kbd> | Close popover |
| <Kbd>Tab</Kbd> | Move focus within content, then out (non-modal) or wrap (modal) |