Popover
A small panel anchored to the control that opened it — it flips and shifts to stay on screen, and its arrow keeps pointing at the trigger.
Installation
Popover ships with the library — install it once, then import:
import { Popover } from "@delacour/react-native-ui/popover";Or copy the source into your project, to own and edit it:
bunx delacour@latest add popoverPopover draws through the overlay layer, so mount OverlayProvider once at the app root.
Usage
import { Button } from "@/components/ui/button";
import { Popover } from "@/components/ui/popover";
import { View } from "react-native";
export default function Example() {
return (
<View className="flex-1 items-center justify-center p-4">
<Popover>
<Popover.Trigger asChild>
<Button variant="secondary">What changed?</Button>
</Popover.Trigger>
<Popover.Content>
<Popover.Arrow />
<Popover.Close />
<Popover.Title>Version 2.4</Popover.Title>
<Popover.Description>Faster sync, offline drafts and a new share sheet.</Popover.Description>
</Popover.Content>
</Popover>
</View>
);
}Anatomy
<Popover>
<Popover.Trigger />
<Popover.Anchor />
<Popover.Content>
<Popover.Arrow />
<Popover.Close />
<Popover.Title />
<Popover.Description />
</Popover.Content>
</Popover>Popover.Anchor is optional — without it the panel anchors to the trigger. Write the arrow, the
close control, the title and the description only where you want them.
Placement
placement is the side the panel prefers — bottom by default — and align lines it up along
that side: start, center or end. It is a preference. When that side lacks room and the
opposite side has more, the panel flips; then it slides along the anchor to stay inside the safe
area, and the arrow keeps pointing at the trigger's centre.
<Popover.Content placement="top" align="start" offset={12}>…</Popover.Content>start and end are logical on top and bottom placements, so they mirror in a right-to-left
layout. alignOffset nudges the panel inward from the aligned edge. usePopover().placement tells
you which side it actually landed on.
Arrow
Popover.Arrow points from the panel at the anchor's centre — not the panel's — and follows a
flip. It stays clear of the panel's rounded corners.
<Popover>
<Popover.Trigger accessibilityLabel="Beta, what does this mean?">
<Badge variant="soft">Beta</Badge>
</Popover.Trigger>
<Popover.Content align="start">
<Popover.Arrow />
<Popover.Title>Beta</Popover.Title>
<Popover.Description>This feature is still changing.</Popover.Description>
</Popover.Content>
</Popover>Width and forms
width takes a number, "trigger" for the anchor's width, "full" for the width of the screen
inside its safe area, or "content-fit", the default. minWidth raises any of them. The keyboard
counts as the bottom of the screen, so a field inside the panel moves it above its trigger rather
than typing under the keyboard.
const [isOpen, setOpen] = useState(false);
<Popover isOpen={isOpen} onOpenChange={setOpen}>
<Popover.Trigger asChild>
<Button className="self-stretch" variant="secondary">{name}</Button>
</Popover.Trigger>
<Popover.Content align="start" minWidth={260} width="trigger">
<Popover.Arrow />
<Popover.Title>Rename</Popover.Title>
<Input autoFocus onChangeText={setDraft} value={draft} />
<View className="flex-row justify-end gap-2">
<Popover.Close asChild>
<Button size="sm" variant="ghost">Cancel</Button>
</Popover.Close>
<Button onPress={save} size="sm">Save</Button>
</View>
</Popover.Content>
</Popover>;Scrolling
maxHeight caps the panel — it is clamped to the room on the side the panel lands on anyway — and
isScrollable scrolls what does not fit. The arrow is lifted out of the scrolling body, so it is
never scrolled away or clipped.
<Popover.Content isScrollable maxHeight={240} minWidth={220}>
<Popover.Arrow />
<ListGroup variant="transparent">
<ListGroup.Item onPress={() => pick("Auckland")}>
<ListGroup.ItemContent>
<ListGroup.ItemTitle>Auckland</ListGroup.ItemTitle>
</ListGroup.ItemContent>
</ListGroup.Item>
</ListGroup>
</Popover.Content>Dismissing
A tap outside the panel closes it, and so do Android's back button and the iOS escape gesture. The
outside tap is swallowed — it never presses what is under it. hasScrim dims the app behind the
panel; without it the app stays visible and an outside tap still closes the popover.
isDismissible={false} leaves only your own controls able to close it.
<Popover isDismissible={false}>…</Popover>
<Popover.Content hasScrim>…</Popover.Content>Custom surface
isUnstyled drops the panel's fill, border, corner and padding. Draw your own behind the content
with background.
<Popover.Content
background={<View className="flex-1 rounded-3xl bg-primary" />}
className="px-5 py-4"
isUnstyled
>
<Text.Label className="text-primary-foreground">Drawn by the caller.</Text.Label>
</Popover.Content>Where it draws
The panel is drawn in the overlay layer, above every bottom sheet and above a dialog it was opened
from, and over the navigator's header. It is measured in window coordinates, so a trigger inside a
ScrollView or a bottom sheet places it correctly. Rotating the device re-places an open popover.
Accessibility
The panel is a modal dialog for assistive technology: focus is held inside it, moves to the title
when it opens and back to the trigger when it closes, and the panel is labelled by
Popover.Title. The trigger reports whether the panel is expanded. The escape gesture closes it.
Under Reduce Motion the panel fades without sliding or scaling.
API Reference
Popover
Prop
Type
Popover.Trigger
Takes Pressable's props. With asChild it hands the toggle to its child's onPress instead of
wrapping it, so the child must be built on Pressable — a Button, a ListGroup.Item.
Popover.Anchor
Takes React Native's ViewProps, plus asChild. While it is mounted the panel anchors to it
instead of the trigger.
Popover.Content
Takes React Native's ViewProps.
Prop
Type
Popover.Arrow
Prop
Type
Popover.Title, Popover.Description
Take Text's props. The title is a Text.Label announced as a header; the description is a muted
Text.Caption.
Popover.Close
Without asChild, a ✕ in the panel's top-right corner, labelled Close. With asChild, it closes
the popover from its child's onPress. Takes Pressable's props either way.
usePopover
Reads the enclosing popover. Throws outside a Popover.
Prop
Type