suiss/uim · React Native Component

Popover

A floating panel anchored to a trigger that shows rich content, such as details or a small form.

Popover is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.

iOS
Popover (open by default) on iOS in light mode

Popover positions a panel next to its PopoverTrigger. PopoverContent renders in a portal, is placed on any side with an align and offsets, and animates in with a short fade, scale and slide using Reanimated. On iOS the content is wrapped in a FullWindowOverlay from react-native-screens, so it draws above native modals.

Use PopoverHeader, PopoverTitle and PopoverDescription to label the panel. For short hints, use Tooltip instead.

Installation

$ pnpm dlx @esuiss/uim@latest add popover

The CLI also adds what it builds on: Text.

Manual

  1. Install the dependencies:
npx expo install cn react-native-reanimated react-native-screens react-native-worklets
  1. Add the pieces it builds on: Text, and the primitives components/primitives/popover.tsx, components/primitives/types.tsx, components/primitives/use-render.tsx.

  2. Copy the source into components/ui/popover.tsx:

uniwind/components/ui/popover.tsx
import { cn } from "cn"
import * as React from "react"
import { Platform, View, type ViewProps } from "react-native"
import { Keyframe, ReduceMotion } from "react-native-reanimated"
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens"
import * as PopoverPrimitive from "@/components/primitives/popover"
import type { Side } from "@/components/primitives/types"
import { resolveClassName } from "@/components/primitives/use-render"
import { AnimatedView } from "@/components/ui/animated-view"
import { TextClassContext } from "@/components/ui/text"

const Popover = PopoverPrimitive.Root

const PopoverTrigger = PopoverPrimitive.Trigger

const FullWindowOverlay =
  1. Update the import paths to match your project.

Usage

import { PopoverContent, PopoverHeader, PopoverTitle, PopoverDescription } from "@/components/ui/popover"
<View className="items-start">
  <Popover defaultOpen>
    <PopoverTrigger render={<Button variant="outline" />}>
      <Text>Open popover</Text>
    </PopoverTrigger>
    <PopoverContent align="start" className="w-80">
      <View className="gap-4">
        <PopoverHeader>
          <PopoverTitle>Dimensions</PopoverTitle>
          <PopoverDescription>
            Set the dimensions for the layer.
          </PopoverDescription>
        </PopoverHeader>
        <View className="gap-2">
          {dimensions.map((dimension) => (
            <View
              key={dimension.id}
              className="flex-row items-center gap-4"
            >
              <Label nativeID={`popover-${dimension.id}`} className="w-24">
                {dimension.label}
              </Label>
              <Input
                accessibilityLabelledBy={`popover-${dimension.id}`}
                defaultValue={dimension.value}
                className="h-8 flex-1"
              />
            </View>
          ))}
        </View>
      </View>
    </PopoverContent>
  </Popover>
</View>

The popover is uncontrolled by default. Use defaultOpen to start open, or open with onOpenChange to control it. onOpenChangeComplete runs after the open or close animation.

Pass a render element to PopoverTrigger, such as a Button, to use your own pressable.

To share one popover between several triggers, create a handle with createHandle from the popover primitive, pass it as handle to the Popover and each PopoverTrigger, and give each trigger a payload. The Popover children can be a function that receives the active payload.

Use portalHost on PopoverContent to render into a named portal host.

Examples

Default

iOS
Popover on iOS in light mode

Sides

Popovers opened on each of the four sides.

iOS
Popover (sides) on iOS in light mode

Simple

A short text popover aligned to the start of its trigger.

iOS
Popover (simple) on iOS in light mode

Detached triggers

Several triggers sharing one popover through a handle, each passing its own payload.

iOS
Popover (detached triggers) on iOS in light mode

API Reference

PopoverContent

Animated panel rendered in a portal and positioned next to the trigger.

ExtendsReact.ComponentProps<typeof PopoverPrimitive.Popup>React.ComponentProps<typeof PopoverPrimitive.Positioner>
PropType
About
Name of the portal host to render into.
Type
string
Default
—

PopoverHeader

Column for the title and description.

ExtendsViewProps

PopoverTitle

Heading that labels the panel.

ExtendsReact.ComponentProps<typeof PopoverPrimitive.Title>

PopoverDescription

Secondary text under the title.

ExtendsReact.ComponentProps<typeof PopoverPrimitive.Description>

Accessibility

  • The panel has the dialog role and is labeled by PopoverTitle when one is present.
  • PopoverTitle has the heading role.
  • When the popover opens, focus moves to the title by default.
  • The popover can close from an outside press, the Android back button, or the screen reader escape gesture.
  • Animations follow the system reduce motion setting.

Notes

  • The panel uses bg-surface-raised, a subtle border-label/5 border (border-label/10 in dark mode) and shadow-lg.
  • FullWindowOverlay is used only on iOS; on Android the content renders in the portal directly.
  • Light and dark colors follow the system appearance.