AnchoredPopover
A controlled popover positioned against any element of the page instead of its trigger.
Anchored is a 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 Popover.
- Ada Jansen
- Bram de Vries
- Chloé Martin
// 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
Popover does not pass that through; Anchored does, together with side,
align and their offsets.
Usage#
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-orlabel aria-).labelledby
Accessibility and keyboard#
Opening moves focus into the popup;
Escapecloses 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..
Import from @workspace/
All props come from the Base UI part this component is built on.
Props
- Type
boolean
Whether the popover is currently open.
- Type
boolean- Default
false
Whether the popover is initially open.
To render a controlled popover, use the open prop instead.
- Type
PayloadChild Render Function<…> ReactNode
The content of the popover. This can be a regular React node or a render function that receives the payload of the active trigger.
- Type
string | null
ID of the trigger that the popover is associated with. This is useful in conjunction with the default prop to create an initially open popover.
- Type
PopoverHandle<unknown>
A handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state.
- Type
"trap-focus" | boolean - Default
false
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 is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled.focus'
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. is rendered inside <Popover.. It can be visually hidden with your own CSS if needed, such as Tailwind's sr- utility.
When modal is 'trap-, render <Popover. inside <Popover. so touch screen readers can escape the popup.
- Type
string | null
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).
- Type
RefObject<…>
A ref to imperative actions.
unmount: Ends the closing phase of the popover after an externally controlled closing animation finishes. CallpreventinUnmount On Close() onfirst, otherwise the popover completes closing on its own. Whether it leaves the DOM is decided byOpen Change keepon the portal.Mounted close: Closes the popover imperatively when called.
Events
Called by the component when something happens.
- Type
(open, eventDetails) => void
Event handler called when the popover is opened or closed.
- Type
(open: boolean) => void
Event handler called after any animations complete when the popover is opened or closed.
Takes only the props listed.
AnchoredPopoverContent#
Import from @workspace/
All props come from the Base UI part this component is built on.
Props
- Type
Align- Default
'center'
How to align the popup relative to the specified side.
- Type
number | OffsetFunction - Default
0
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.: the dimensions of the anchor element with propertiesanchor widthandheight.data.: the dimensions of the positioner element with propertiespositioner widthandheight.data.: which side of the anchor element the positioner is aligned against.side data.: how the positioner is aligned relative to the specified side.align
- Type
ElementRefObject<Element | null> VirtualElement () => …null
An element to position the popup against. By default, the popup will be positioned against the trigger.
- Type
booleanRefObject<HTMLElement | null> (closeType) => …
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).Ref: Move focus to the ref element.Object function: Called with the interaction type (mouse,touch,pen, orkeyboard). Return an element to focus,trueto use the default behavior,nullto fall back to the default behavior, orfalse/undefinedto do nothing.
- Type
booleanRefObject<HTMLElement | null> (openType) => …
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).Ref: Move focus to the ref element.Object function: Called with the interaction type (mouse,touch,pen, orkeyboard). Return an element to focus,trueto use the default behavior,nullto fall back to the default behavior, orfalse/undefinedto do nothing.
- Type
Side- Default
'bottom'
Which side of the anchor element to align the popup against. May automatically change to avoid collisions.
- Type
number | OffsetFunction - Default
0
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.: the dimensions of the anchor element with propertiesanchor widthandheight.data.: the dimensions of the positioner element with propertiespositioner widthandheight.data.: which side of the anchor element the positioner is aligned against.side data.: how the positioner is aligned relative to the specified side.align
- Type
ComponentRender Fn<…> ReactElement<…>
Allows you to replace the component's HTML element with a different tag, or compose it with another component.
Accepts a React or a function that returns the element to render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.