---
id: "packages/active-ui/anchored-popover"
title: "AnchoredPopover"
description: "A controlled popover positioned against any element of the page instead of its trigger."
url: "/packages/active-ui/anchored-popover"
locale: "en"
revision: "c8c8cb8214d37273"
---

# AnchoredPopover

`AnchoredPopover` is a [Popover](/packages/active-ui/popover) that floats next to an element that is
not its trigger: a table row, a node in an editor, a marker in a chart. You control whether it is
open and pass the element as its `anchor`; it has the look of `PopoverContent`.

**Example: A note anchored to its row**

Client: Runs entirely in your browser. Source: `examples/ui/anchored-popover.tsx`.

```tsx
// apps/docs/examples/ui/anchored-popover.tsx
'use client';

import {useState} from 'react';

import {
  AnchoredPopover,
  AnchoredPopoverContent,
} from '@workspace/active-ui/components/composed/anchored-popover';
import {Button} from '@workspace/active-ui/components/primitives/button';

const ROWS = [
  {id: 'r1', name: 'Ada Jansen', note: 'Address changed on 3 March.'},
  {
    id: 'r2',
    name: 'Bram de Vries',
    note: 'Statement returned as undeliverable.',
  },
  {id: 'r3', name: 'Chloé Martin', note: 'No remarks.'},
];

/**
 * A popover that floats next to an element that is not its trigger: here
 * the whole row, opened by the button at its end. The popover is
 * controlled; the row is its `anchor`.
 */
export default function AnchoredPopoverExample() {
  const [open, setOpen] = useState<{
    id: string;
    row: HTMLElement;
  } | null>(null);
  const note = ROWS.find((row) => row.id === open?.id);
  return (
    <>
      <ul className="w-full max-w-md divide-y rounded-md border">
        {ROWS.map((row) => (
          <li
            key={row.id}
            className="flex items-center justify-between gap-3 px-3 py-2 text-sm"
          >
            {row.name}
            <Button
              size="sm"
              variant="ghost"
              aria-expanded={open?.id === row.id}
              onClick={(event) => {
                // The row around the button is the anchor.
                const element = event.currentTarget.closest('li');
                setOpen(
                  open?.id === row.id || !element
                    ? null
                    : {id: row.id, row: element},
                );
              }}
            >
              Note
            </Button>
          </li>
        ))}
      </ul>
      <AnchoredPopover
        open={open !== null}
        onOpenChange={(next) => {
          if (!next) setOpen(null);
        }}
      >
        <AnchoredPopoverContent
          anchor={open?.row}
          side="bottom"
          align="end"
          aria-label={note ? `Note on ${note.name}` : undefined}
        >
          <p className="font-medium">{note?.name}</p>
          <p className="text-muted-foreground">{note?.note}</p>
        </AnchoredPopoverContent>
      </AnchoredPopover>
    </>
  );
}
```

## What the composition adds

Base UI positions a popover against its trigger unless the positioner gets an `anchor`. The stock
`PopoverContent` does not pass that through; `AnchoredPopoverContent` does, together with `side`,
`align` and their offsets.

## Usage

```tsx
import {
  AnchoredPopover,
  AnchoredPopoverContent,
} from '@workspace/active-ui/components/composed/anchored-popover';

<AnchoredPopover open={open} onOpenChange={setOpen}>
  <AnchoredPopoverContent
    anchor={rowElement}
    side="bottom"
    align="end"
    aria-label="Note"
  >
    …
  </AnchoredPopoverContent>
</AnchoredPopover>;
```

## What your application provides

- The open state, the element to anchor to, and the control that opens it, with `aria-expanded`.
- A name for the popup (`aria-label` or `aria-labelledby`).

## Accessibility and keyboard

- Opening moves focus into the popup; `Escape` closes it and returns focus to the element that had
  it, here the button that opened it. A click outside closes it too.
- The popup is a `dialog`; give it a name.

## API

### AnchoredPopover

The root is Base UI's `Popover.Root`.

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `actionsRef` | `RefObject<PopoverRootActions \| null>` | no | 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. |
| `children` | `PayloadChildRenderFunction<unknown> \| ReactNode` | no | The content of the popover. This can be a regular React node or a render function that receives the `payload` of the active trigger. |
| `defaultOpen` | `boolean` | no | Whether the popover is initially open.  To render a controlled popover, use the `open` prop instead. |
| `defaultTriggerId` | `string \| null` | no | 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` | `PopoverHandle<unknown>` | no | A handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state. |
| `modal` | `"trap-focus" \| boolean` | no | 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. |
| `onOpenChange` | `((open: boolean, eventDetails: PopoverRoot.ChangeEventDetails) => void)` | no | Event handler called when the popover is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` | no | Event handler called after any animations complete when the popover is opened or closed. |
| `open` | `boolean` | no | Whether the popover is currently open. |
| `triggerId` | `string \| null` | no | 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). |

### AnchoredPopoverContent

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `align` | `Align` | no | How to align the popup relative to the specified side. |
| `alignOffset` | `number \| OffsetFunction` | no | 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. |
| `anchor` | `Element \| RefObject<Element \| null> \| VirtualElement \| (() => Element \| VirtualElement \| null) \| null` | no | An element to position the popup against. By default, the popup will be positioned against the trigger. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => void \| boolean \| HTMLElement \| null)` | no | 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. |
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => void \| boolean \| HTMLElement \| null)` | no | 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. |
| `render` | `ComponentRenderFn<HTMLProps, PopoverPopupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| `side` | `Side` | no | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. |
| `sideOffset` | `number \| OffsetFunction` | no | 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. |
