# Popover

Things that open next to the button that opened them, without blocking the page: menus, pickers, extra detail. Built on the native `popover` attribute, so a click outside or Escape closes it.

Renders: `div` · Intents: [4](#intents-it-renders) · Domains: [`popover.*`](https://www.prongs.dev/docs/intents/popover.md)

## Examples

### A menu of actions

The trigger is the [`<Action>`](https://www.prongs.dev/docs/components/action.md) that opens it. Pressing an item closes the menu, and so do a click outside and Escape.

```tsx
function Menu() {
  const share = () => {};
  const edit = () => {};
  const remove = () => {};
  return (
    <Popover
      intent="popover.menu"
      label="Article actions"
      trigger={<Action intent="action.reveal" label="More actions" icon={<Ellipsis aria-hidden className="size-4" />} />}
    >
      <Action intent="action.share" onClick={share}>Share</Action>
      <Action intent="action.edit" onClick={edit}>Edit</Action>
      <Action intent="action.delete" onClick={remove}>Delete</Action>
    </Popover>
  );
}
```

### Picking one

`options` as data, `value` and `onChange`. Picking closes it, and `selectionSummary` writes the trigger text.

```tsx
function Picker() {
  const [order, setOrder] = useState('newest');
  return (
    <Popover
      intent="popover.picker"
      label="Sort order"
      trigger={<Action intent="choice.open">{selectionSummary(ORDERS, order, 'Sort by')}</Action>}
      options={ORDERS}
      value={order}
      onChange={(next) => setOrder(next as string)}
    />
  );
}
```

### Picking several

[`popover.picker.many`](https://www.prongs.dev/docs/intents/popover/picker-many.md) stays open while you pick. The intent decides one or many, never a prop.

```tsx
function PickMany() {
  const [sources, setSources] = useState<string[]>(['Wired']);
  return (
    <Popover
      intent="popover.picker.many"
      label="Sources"
      trigger={<Action intent="choice.open">{selectionSummary(SOURCES, sources, 'All sources')}</Action>}
      options={SOURCES}
      value={sources}
      onChange={(next) => setSources(next as string[])}
    />
  );
}
```

### Detail on demand

[`popover.info`](https://www.prongs.dev/docs/intents/popover/info.md) holds a few words about something, opened only when asked for.

```tsx
<Layout intent="layout.group" className="flex-row items-center">
  <Text intent="text.label">Daily digest</Text>
  <Popover intent="popover.info" label="About the daily digest" trigger={<Action intent="action.reveal">What is this?</Action>}>
    <Text intent="text.body">One email each morning with the articles your sources published since the last one.</Text>
  </Popover>
</Layout>
```

## Intents it renders

One domain, with its own page of neighbours and examples. The intent decides the element; these are the starter intents.

### Popover [`popover.*`](https://www.prongs.dev/docs/intents/popover.md)

4 intents

| Intent | Use it for | Resolves to |
|---|---|---|
| [`popover.menu`](https://www.prongs.dev/docs/intents/popover/menu.md) | A short list of commands or links that opens from a button: more actions, an account menu. Closes when one is pressed | `<div popover>` |
| [`popover.picker`](https://www.prongs.dev/docs/intents/popover/picker.md) | Choices that open from a button, one of which is picked: a sort order, a category. Picking one closes it | `<div popover>` |
| [`popover.picker.many`](https://www.prongs.dev/docs/intents/popover/picker-many.md) | Choices that open from a button, any number of which can be picked: sources, tags. Stays open while picking | `<div popover>` |
| [`popover.info`](https://www.prongs.dev/docs/intents/popover/info.md) | More detail about something, opened on demand: what a setting does, where a number comes from | `<div popover>` |

## Props

Plus its element’s usual attributes (`id`, `onClick`, `aria-*`, `data-*`). No `className` or `style`.

| Prop | Type | What it does |
|---|---|---|
| `intent` (required) | `PopoverIntent` | [`popover.menu`](https://www.prongs.dev/docs/intents/popover/menu.md), [`popover.picker`](https://www.prongs.dev/docs/intents/popover/picker.md) or [`popover.info`](https://www.prongs.dev/docs/intents/popover/info.md). |
| `trigger` (required) | `ReactElement` | The [`<Action>`](https://www.prongs.dev/docs/components/action.md) that opens it. Popover connects the two and places itself next to it. |
| `label` (required) | `string` | The accessible name: what it holds, like "Article actions". |
| `children` | `ReactNode` | The content: [`<Action>`](https://www.prongs.dev/docs/components/action.md)s for a menu, choices for a picker, text for detail. |
| `options` | `{ value, label?, disabled? }[]` | A picker’s choices as data, for lists from a database or a CMS. They render as a listbox (`role="option"`, `aria-selected`), announced like a native select. |
| `value` | `string \| string[]` | A picker’s choice: one value, or several when its intent picks many ([`popover.picker.many`](https://www.prongs.dev/docs/intents/popover/picker-many.md)). |
| `onChange` | `(value) => void` | Called with the new choice, or with every picked value when its intent picks many. |
| `keepOpen` | `boolean` | Stay open when something inside is pressed. Pickers that pick many stay open anyway. |
| `unsafeClassName` | `string` | Bypasses the intent. Needs a `// prongs-unsafe: <reason>` comment on the line above, and `prongs check` counts it. |

## Rules

- The trigger is an [`<Action>`](https://www.prongs.dev/docs/components/action.md) with a button, never a link. A picker’s trigger is [`choice.open`](https://www.prongs.dev/docs/intents/choice/open.md), with `selectionSummary(options, value, placeholder)` as its text.
- Whether a picker picks one or many comes from its intent, never a prop.
- The arrow keys, Home, End and first letters move between items; Escape or a press closes it and focus goes back to the trigger.
- Inside a menu, actions look like menu items whatever their intent; the menu decides.
- Anything that needs the user’s full attention is a [`<Dialog>`](https://www.prongs.dev/docs/components/dialog.md), not a popover.
