Popover

Show content with a trigger in a floating pane

import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from '@tamagui/lucide-icons'
import type { PopoverProps } from 'tamagui'
import { Adapt, Button, Input, Label, Popover, XStack, YStack } from 'tamagui'
export function PopoverDemo() {
return (
<XStack space="$2" flex={1} justifyContent="center" alignItems="center">
<Demo placement="left" Icon={ChevronLeft} Name="left-popover" />
<Demo placement="bottom" Icon={ChevronDown} Name="bottom-popover" />
<Demo placement="top" Icon={ChevronUp} Name="top-popover" />
<Demo placement="right" Icon={ChevronRight} Name="right-popover" />
</XStack>
)
}
export function Demo({
Icon,
Name,
...props
}: PopoverProps & { Icon?: any; Name?: string }) {
return (
<Popover size="$5" allowFlip {...props}>
<Popover.Trigger asChild>
<Button icon={Icon} />
</Popover.Trigger>
<Adapt when="sm" platform="touch">
<Popover.Sheet modal dismissOnSnapToBottom>
<Popover.Sheet.Frame padding="$4">
<Adapt.Contents />
</Popover.Sheet.Frame>
<Popover.Sheet.Overlay animation="lazy" enterStyle={{ opacity: 0 }} exitStyle={{ opacity: 0 }} />
</Popover.Sheet>
</Adapt>
<Popover.Content borderWidth={1} borderColor="$borderColor" enterStyle={{ y: -10, opacity: 0 }} exitStyle={{ y: -10, opacity: 0 }} elevate animation={[ 'quick', { opacity: { overshootClamping: true, }, }, ]} >
<Popover.Arrow borderWidth={1} borderColor="$borderColor" />
<YStack space="$3">
<XStack space="$3">
<Label size="$3" htmlFor={Name}>
Name
</Label>
<Input size="$3" id={Name} />
</XStack>
<Popover.Close asChild>
<Button size="$3" onPress={() => { /* Custom code goes here, does not interfere with popover closure */ }} >
Submit
</Button>
</Popover.Close>
</YStack>
</Popover.Content>
</Popover>
)
}

Features

  • Optional arrow to point to content.

  • Keeps within bounds of page.

  • Can be placed into 12 anchor positions.

Popovers are a great way to show content that's only visible when trigger is pressed, floating above the current content.

Installation

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

yarn @tamagui/popover

PortalProvider

When rendering into root of app instead of inline, you'll first need to install the @tamagui/portal package:

yarn @tamagui/portal

Then add PortalProvider to the root of your app:

App.tsx

import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
<PortalProvider shouldAddRootHost>
<YourApp />
</PortalProvider>
)
}
export default App

Props

  • shouldAddRootHost

    boolean

    Defines whether to add a default root host or not.

  • Anatomy

    import { Popover } from 'tamagui' // or '@tamagui/popover'
    export default () => (
    <Popover>
    <Popover.Trigger />
    <Popover.Content>
    <Popover.Arrow />
    <Popover.Close />
    {/* ScrollView is optional, can just put any contents inside if not scrollable */}
    <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
    {/* ... */}
    </Popover.Content>
    {/* optionally change to sheet when small screen */}
    <Popover.Adapt when="sm">
    <Popover.Sheet>
    <Popover.Sheet.Overlay />
    <Popover.Sheet.Frame>
    <Popover.Sheet.ScrollView>
    <Popover.Adapt.Contents />
    </Popover.Sheet.ScrollView>
    </Popover.Sheet.Frame>
    </Popover.Sheet>
    </Popover.Adapt>
    </Popover>
    )

    API Reference

    Popover

    Contains every component for the popover.

    Props

  • children (required)

    React.ReactNode

    Must contain Popover.Content

  • size

    SizeTokens

    Passes size down too all sub-components when set for padding, arrow, borderRadius

  • placement

    Placement

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

  • open

    boolean

  • defaultOpen

    boolean

  • onOpenChange

    (open: boolean) => void

  • modal

    boolean

    Default: 

    true

    Renders into root of app instead of inline.

  • keepChildrenMounted

    boolean

    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.

  • stayInFrame

    ShiftProps | boolean

    Keeps the Popover inside the frame, see floating-ui shift().

  • 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 }

  • If using modal={true} (which is true by default), refer to the PortalProvider installation for more information.

    Popover.Trigger

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

    Popover.Content

    Renders as SizableStack which is just a YStack (see Stacks) with an extra size prop that accepts any SizeTokens.

    Used to display the content of the popover.

    Props

  • size

    SizeTokens

  • unstyled

    boolean

    Removes all default Tamagui styles.

  • 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.

    Popover.Sheet

    When used with Adapt, Popover will render as a sheet when that breakpoint is active.

    See Sheet for more props.

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

    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 it's own ScrollView that handles special logic for interactions with dragging.

    Previous

    Dialog

    Next

    Sheet