Popover

Show content with a trigger in a floating pane

Popover positions focused content around a trigger and can adapt the same content into a Sheet on smaller screens.

Features

  • Optional arrow pointing to content

  • Positioning within page bounds

  • Twelve anchor positions

  • Shared Sheet handoff with Dialog and Select

Popover shows content only while its trigger is pressed, floating above the current content. It automatically stacks above other content.

Popovers are not a recommended pattern for mobile apps. Use Adapt to render them as a Sheet instead, or conditionally render native UI.

Installation

Popover is already installed in tamagui, or you can install it independently:

yarn add @tamagui/popover

For native apps, we recommend setting up native portals to preserve React context inside Popover content.

Anatomy

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/Popover.tsx (registry item "popover")
// components/tamagui/Sheet.tsx (registry item "sheet")
// npm dependencies: @tamagui/core @tamagui/popover @tamagui/sheet
import { Adapt } from 'tamagui'; // or '@tamagui/popover'
import { Popover } from "../components/tamagui/Popover";
import { Sheet } from "../components/tamagui/Sheet";
export default () => <Popover>
<Popover.Trigger />
{/* Optional: Control focus behavior */}
<Popover.FocusScope loop trapped focusOnIdle={true}>
<Popover.Content>
<Popover.Arrow />
<Popover.Close />
{/* ScrollView is optional, can just put any contents inside if not scrollable */}
<Popover.ScrollView>{/* ... */}</Popover.ScrollView>
{/* ... */}
</Popover.Content>
</Popover.FocusScope>
{/* optionally change to sheet when small screen */}
{/* you can also use <Popover.Adapt /> */}
<Adapt when="max-md">
<Sheet>
<Sheet.Overlay />
<Sheet.Container>
<Sheet.Background />
<Sheet.ScrollView>
<Adapt.Contents />
</Sheet.ScrollView>
</Sheet.Container>
</Sheet>
</Adapt>
</Popover>;

Scoping

Popover supports scoping which lets you mount one or more Popover instances at the root of your app, while having a deeply nested child Trigger or Content attach to the proper parent Popover instance.

In performance sensitive areas you may want to take advantage of this as it allows you to only render the Popover.Trigger inside the sensitive area. Popover isn’t the cheapest component - it has a lot of functionality inside of it like scroll management, focus management, and tracking position.

Here’s the basic anatomy of using scope and placing your Popover higher up for performance:

_layout.tsx

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/Popover.tsx (registry item "popover")
// npm dependencies: @tamagui/core @tamagui/popover
import { Popover } from "../components/tamagui/Popover"; // in your root layout:
export default ({
children
}) => <Popover scope="user-avatar">
<Popover.Content>
<Popover.Arrow />
<Popover.Close />
<Popover.ScrollView>{/* ... */}</Popover.ScrollView>
</Popover.Content>
{/* the rest of your app, note that it's inside of Popover */}
{children}
</Popover>;

UserAvatar.tsx

export default () => (
<Popover.Trigger scope="user-avatar">
<Avatar />
</Popover.Trigger>
)

Note that the Trigger scope ties to the Popover scope.

API reference

In v3, Popover uses the same Adapt handoff path as Dialog. Adapted content stays mounted through the sheet exit animation and unmounts after the sheet reports that it is fully hidden.

Popover

Contains every component for the popover.

Props

  • children (required)

    React.ReactNode

    Must contain Popover.Content

  • placement

    Placement

    'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'

  • open

    boolean

    Controlled open state of the popover.

  • defaultOpen

    boolean

    Initial open state when uncontrolled.

  • onOpenChange

    (open: boolean, via?: 'hover' | 'press') => void

    Called when the popover opens or closes. The optional second argument indicates how it was triggered - 'hover' for hover events (when using the hoverable prop) or 'press' for click/press events.

  • keepChildrenMounted

    boolean | "lazy"

    By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render. The "lazy" value will only initially mount the children after a React startTransition, and then keep them mounted thereafter.

  • disableDismissable

    boolean

    Disables the dismissable layer (escape key, outside click handling). Useful when using keepChildrenMounted for popovers that stay mounted but are visually hidden - set this to true when the popover is hidden to prevent it from capturing escape key presses.

  • stayInFrame

    ShiftProps | boolean

    Default: 

    { padding: 10 }

    Shifts the popover horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).

  • allowFlip

    FlipProps | boolean

    Moves the Popover to other sides when space allows it, see floating-ui flip().

  • offset

    OffsetOptions

    Determines the distance the Popover appears from the target, see floating-ui offset().

  • hoverable

    boolean | UseFloatingProps

    Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }

  • disableFocus

    boolean

    Default: 

    !hoverable

    Disable opening when the trigger receives keyboard focus. Ordinary popovers open on click, Enter, or Space. Set false to open on focus; hoverable popovers enable this behavior by default.

  • resize

    SizeProps | boolean

    Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().

  • zIndex

    number

    Override the automatic z-index stacking. By default, Tamagui automatically stacks overlays so later-opened content appears above earlier content. Only set this if you need to override the automatic behavior.

  • For most of these properties, you’ll want to reference the floating-ui docs.

    Popover.Arrow

    Popover.Arrow can be used to show an arrow that points at the Trigger element. In order for the Arrow to show you must have a Trigger element within your Popover. Arrows extend YStack, see Stacks.

    Props

  • animatePosition

    boolean

    Enable smooth animation when the arrow position changes.

  • size

    number

    Size of the arrow in pixels.

  • offset

    number

    Offset from the content edge.

  • Popover.Trigger

    Used to trigger opening of the popover when uncontrolled, just renders a YStack, see Stacks.

    Popover.Content

    Extends PopperContent which extends a YStack (see Stacks). Used to display the content of the popover.

    Props

  • animatePosition

    boolean | 'even-when-repositioning'

    Enable smooth animation when the content position changes (e.g., when flipping sides).

  • transformOrigin

    boolean

    Default: 

    true

    Automatically sets CSS transform-origin based on placement and arrow position. Updates when the popover flips. Enables natural scale animations that grow from the arrow point.

  • unstyled

    boolean

    Removes all default Tamagui styles.

  • trapFocus

    boolean

    Whether focus should be trapped within the `Popover`

  • disableFocusScope

    boolean

    Whether popover should not focus contents on open

  • onOpenAutoFocus

    FocusScopeProps['onMountAutoFocus']

    Event handler called when auto-focusing on open. Can be prevented.

  • onCloseAutoFocus

    FocusScopeProps['onUnmountAutoFocus'] | false

    Event handler called when auto-focusing on close. Can be prevented.

  • lazyMount

    boolean

    Delays mounting content until first open.

  • forceMount

    boolean

    Disables part presence gating so the content stays mounted. Matches Dialog forceMount semantics.

  • Popover.Anchor

    Renders as YStack, see Stacks.

    When you want the Trigger to be in another location from where the Popover attaches, use Anchor. When used, Anchor is where the Popover will attach, while Trigger will open it.

    Sheet (with Adapt)

    When used with Adapt, you can render a Sheet when that breakpoint is active. Import Sheet directly from tamagui or @tamagui/sheet.

    See Sheet for more props.

    Must use Adapt.Contents inside the Sheet.Container to insert the contents given to Popover.Content

    Popover.FocusScope

    Provides access to the underlying FocusScope component used by Popover for focus management. Can be used to control focus behavior from a parent component.

    Props

  • enabled

    boolean

    Default: 

    true

    Whether focus management is enabled

  • loop

    boolean

    Default: 

    false

    When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable

  • trapped

    boolean

    Default: 

    false

    When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus

  • noFocus

    boolean

    Default: 

    false

    Zero focus mode. While active, focus is allowed neither inside nor outside the scope. Web only.

  • focusOnIdle

    boolean | number

    Default: 

    true

    When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations

  • onMountAutoFocus

    (event: Event) => void

    Event handler called when auto-focusing on mount. Can be prevented

  • onUnmountAutoFocus

    (event: Event) => void

    Event handler called when auto-focusing on unmount. Can be prevented

  • Popover.ScrollView

    Must be nested inside Content. Renders as a plain React Native ScrollView. If used alongside <Adapt /> and Sheet, Tamagui will automatically know to remove this ScrollView when swapping into the Sheet, as the Sheet must use its own ScrollView that handles special logic for interactions with dragging.

    Utility functions

    These functions allow you to programmatically manage open popovers.

    import {
    closeOpenPopovers,
    closeLastOpenedPopover,
    hasOpenPopovers,
    } from '@tamagui/popover'

    closeOpenPopovers

    Closes all currently open popovers. Returns true if any popovers were closed, false if none were open.

    const didClose = closeOpenPopovers()

    closeLastOpenedPopover

    Closes only the most recently opened popover. Returns true if a popover was closed, false if none were open.

    const didClose = closeLastOpenedPopover()

    hasOpenPopovers

    Returns true if there are any open popovers, false otherwise.

    if (hasOpenPopovers()) {
    // handle open popovers
    }

    Source

    v2-look Popover: token-based padding and radius with theme background and arrow border styling, over the unstyled @tamagui/ui Popover behavior. This is the exact source the registry ships — copy it into your app and the examples above import from your copy.

    components/tamagui/Popover.tsx
    import { createRefComponent, styled, withStaticProperties } from '@tamagui/core' import { Popover as UiPopover } from '@tamagui/popover' import * as React from 'react' export const PopoverContent = styled(UiPopover.Content, { displayName: 'PopoverContent', padding: '4', borderRadius: '4', backgroundColor: 'background', alignItems: 'center', }) export const PopoverArrow = styled(UiPopover.Arrow, { displayName: 'PopoverArrow', backgroundColor: 'background', borderColor: 'border-color', }) const PopoverRoot = createRefComponent< React.ComponentRef<typeof UiPopover>, React.ComponentProps<typeof UiPopover> >(function Popover(props, ref) { return <UiPopover {...props} ref={ref} /> }) // keep the ref-handle type on the same name so `useRef<Popover>` still works export type Popover = UiPopover export const Popover = withStaticProperties(PopoverRoot, { Anchor: UiPopover.Anchor, Arrow: PopoverArrow, Trigger: UiPopover.Trigger, Content: PopoverContent, Close: UiPopover.Close, Adapt: UiPopover.Adapt, ScrollView: UiPopover.ScrollView, FocusScope: UiPopover.FocusScope, })

    Dependencies

    yarn add @tamagui/core @tamagui/popover

    Expects theme tokens: background, border-color. Native: requires a Portal provider at the app root for the popover to mount above content

    Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).