Introduction
Components
- Accordion
- Action Sheet
- Alert
- Audio Player
- Audio Recorder
- Audio Waveform
- Avatar
- Badge
- BottomSheet
- Button
- Camera
- Camera Preview
- Card
- Carousel
- Checkbox
- Collapsible
- Color Picker
- Combobox
- Date Picker
- File Picker
- Gallery
- Hello Wave
- Icon
- Image
- Input
- Input OTP
- Link
- MediaPicker
- Mode Toggle
- Onboarding
- ParallaxScrollView
- Picker
- Popover
- Progress
- Radio
- ScrollView
- SearchBar
- Separator
- Share
- Sheet
- Skeleton
- Spinner
- Switch
- Table
- Tabs
- Text
- Toast
- Toggle
- Video
- View
Charts
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverBody,
PopoverClose,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import React from 'react';
export function PopoverDemo() {
return (
<Popover>
<PopoverTrigger asChild>
<Button>Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<Text variant='title'>Popover Title</Text>
</PopoverHeader>
<PopoverBody>
<Text>
This is the popover content. You can put any content here.
</Text>
</PopoverBody>
<PopoverFooter>
<PopoverClose>
<Button variant='outline' size='sm'>
Close
</Button>
</PopoverClose>
</PopoverFooter>
</PopoverContent>
</Popover>
);
}
Installation
pnpm dlx bna-ui add popover
Usage
import {
Popover,
PopoverBody,
PopoverClose,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
} from '@/components/ui/popover';
<Popover>
<PopoverTrigger>
<Button>Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<Text>Popover Title</Text>
</PopoverHeader>
<PopoverBody>
<Text>This is the popover content.</Text>
</PopoverBody>
<PopoverFooter>
<PopoverClose>
<Button variant='outline'>Close</Button>
</PopoverClose>
</PopoverFooter>
</PopoverContent>
</Popover>
Examples
Default
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverBody,
PopoverClose,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import React from 'react';
export function PopoverDemo() {
return (
<Popover>
<PopoverTrigger asChild>
<Button>Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<Text variant='title'>Popover Title</Text>
</PopoverHeader>
<PopoverBody>
<Text>
This is the popover content. You can put any content here.
</Text>
</PopoverBody>
<PopoverFooter>
<PopoverClose>
<Button variant='outline' size='sm'>
Close
</Button>
</PopoverClose>
</PopoverFooter>
</PopoverContent>
</Popover>
);
}
Positioning
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
export function PopoverPositioning() {
return (
<View style={{ gap: 16, alignItems: 'center' }}>
<Popover>
<PopoverTrigger asChild>
<Button style={{ width: 200 }}>Top</Button>
</PopoverTrigger>
<PopoverContent side='top'>
<PopoverBody>
<Text>Popover content positioned on top</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<View style={{ flexDirection: 'row', gap: 16 }}>
<Popover>
<PopoverTrigger asChild>
<Button style={{ flex: 1 }}>Left</Button>
</PopoverTrigger>
<PopoverContent side='left'>
<PopoverBody>
<Text>Left positioned</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button style={{ flex: 1 }}>Right</Button>
</PopoverTrigger>
<PopoverContent side='right'>
<PopoverBody>
<Text>Right positioned</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</View>
<Popover>
<PopoverTrigger asChild>
<Button style={{ width: 200 }}>Bottom</Button>
</PopoverTrigger>
<PopoverContent side='bottom'>
<PopoverBody>
<Text>Popover content positioned on bottom</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</View>
);
}
Alignment
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
export function PopoverAlignment() {
return (
<View style={{ gap: 24, alignItems: 'center' }}>
<View style={{ gap: 16 }}>
<Text variant='title'>Bottom Side Alignment</Text>
<View style={{ flexDirection: 'row', gap: 16 }}>
<Popover>
<PopoverTrigger asChild>
<Button>Start</Button>
</PopoverTrigger>
<PopoverContent side='bottom' align='start'>
<PopoverBody>
<Text>Aligned to start (left)</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>Center</Button>
</PopoverTrigger>
<PopoverContent side='bottom' align='center'>
<PopoverBody>
<Text>Aligned to center</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>End</Button>
</PopoverTrigger>
<PopoverContent side='bottom' align='end'>
<PopoverBody>
<Text>Aligned to end (right)</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</View>
</View>
<View style={{ gap: 16 }}>
<Text variant='title'>Right Side Alignment</Text>
<View style={{ gap: 16 }}>
<Popover>
<PopoverTrigger asChild>
<Button>Start</Button>
</PopoverTrigger>
<PopoverContent side='right' align='start'>
<PopoverBody>
<Text>Aligned to start (top)</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>Center</Button>
</PopoverTrigger>
<PopoverContent side='right' align='center'>
<PopoverBody>
<Text>Aligned to center</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>End</Button>
</PopoverTrigger>
<PopoverContent side='right' align='end'>
<PopoverBody>
<Text>Aligned to end (bottom)</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</View>
</View>
</View>
);
}
Controlled
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverBody,
PopoverContent,
PopoverHeader,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
export function PopoverControlled() {
const [isOpen, setIsOpen] = useState(false);
return (
<View style={{ gap: 16, alignItems: 'center' }}>
<View style={{ flexDirection: 'row', gap: 12 }}>
<Button
variant={isOpen ? 'default' : 'outline'}
onPress={() => setIsOpen(true)}
>
Open
</Button>
<Button
variant={!isOpen ? 'default' : 'outline'}
onPress={() => setIsOpen(false)}
>
Close
</Button>
</View>
<Text>Status: {isOpen ? 'Open' : 'Closed'}</Text>
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<Button>Controlled Popover</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<Text variant='title'>Controlled Popover</Text>
</PopoverHeader>
<PopoverBody>
<Text>
This popover's state is controlled externally. You can open and
close it using the buttons above or by clicking the trigger.
</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</View>
);
}
Custom Content
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useThemeColor } from '@/hooks/useThemeColor';
import React from 'react';
export function PopoverCustom() {
const primaryColor = useThemeColor({}, 'primary');
const mutedColor = useThemeColor({}, 'muted');
return (
<View style={{ gap: 16, alignItems: 'center' }}>
<Popover>
<PopoverTrigger asChild>
<Button>Custom Styled</Button>
</PopoverTrigger>
<PopoverContent
style={{
backgroundColor: primaryColor,
borderRadius: 16,
maxWidth: 250,
}}
>
<PopoverBody style={{ padding: 20 }}>
<Text style={{ color: 'black', textAlign: 'center' }}>
This popover has custom styling with primary color background
</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button variant='outline'>Large Content</Button>
</PopoverTrigger>
<PopoverContent
maxWidth={400}
maxHeight={300}
style={{
backgroundColor: mutedColor,
}}
>
<PopoverBody style={{ padding: 24 }}>
<Text variant='title' style={{ marginBottom: 12 }}>
Large Popover Content
</Text>
<Text style={{ lineHeight: 20 }}>
This popover has custom dimensions and can hold more content. It
demonstrates how you can customize the appearance and size of
popover components to fit your design needs.
</Text>
<Text style={{ marginTop: 12, fontStyle: 'italic' }}>
The content area is scrollable if it exceeds the maximum height.
</Text>
</PopoverBody>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button size='icon'>?</Button>
</PopoverTrigger>
<PopoverContent side='top' align='center'>
<PopoverBody>
<Text style={{ textAlign: 'center' }}>
This is a help tooltip using a custom circular trigger button
</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</View>
);
}
Form Content
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Popover,
PopoverBody,
PopoverClose,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
export function PopoverForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = () => {
// Handle form submission
console.log('Form submitted:', { name, email });
// Reset form
setName('');
setEmail('');
};
return (
<Popover>
<PopoverTrigger asChild>
<Button>Add Contact</Button>
</PopoverTrigger>
<PopoverContent maxWidth={320}>
<PopoverHeader>
<Text variant='title'>Add New Contact</Text>
</PopoverHeader>
<PopoverBody>
<View style={{ gap: 16 }}>
<View>
<Text style={{ marginBottom: 8, fontWeight: '500' }}>Name</Text>
<Input
placeholder='Enter full name'
value={name}
onChangeText={setName}
/>
</View>
<View>
<Text style={{ marginBottom: 8, fontWeight: '500' }}>Email</Text>
<Input
placeholder='Enter email address'
value={email}
onChangeText={setEmail}
keyboardType='email-address'
autoCapitalize='none'
/>
</View>
</View>
</PopoverBody>
<PopoverFooter>
<PopoverClose asChild>
<Button variant='outline' size='sm'>
Cancel
</Button>
</PopoverClose>
<PopoverClose asChild>
<Button
size='sm'
onPress={handleSubmit}
disabled={!name.trim() || !email.trim()}
>
Add Contact
</Button>
</PopoverClose>
</PopoverFooter>
</PopoverContent>
</Popover>
);
}
Menu Style
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverClose,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useThemeColor } from '@/hooks/useThemeColor';
import React from 'react';
import { TouchableOpacity } from 'react-native';
interface MenuItemProps {
icon: string;
label: string;
onPress: () => void;
destructive?: boolean;
}
function MenuItem({
icon,
label,
onPress,
destructive = false,
}: MenuItemProps) {
const textColor = useThemeColor(
{},
destructive ? 'destructive' : 'foreground'
);
const mutedColor = useThemeColor({}, 'muted');
return (
<PopoverClose asChild>
<TouchableOpacity
style={{
flexDirection: 'row',
alignItems: 'center',
padding: 12,
borderRadius: 6,
}}
onPress={onPress}
activeOpacity={0.7}
>
<Text
style={{
fontSize: 16,
marginRight: 12,
width: 20,
textAlign: 'center',
}}
>
{icon}
</Text>
<Text style={{ color: textColor, fontSize: 14 }}>{label}</Text>
</TouchableOpacity>
</PopoverClose>
);
}
export function PopoverMenu() {
const borderColor = useThemeColor({}, 'border');
const handleMenuAction = (action: string) => {
console.log(`Menu action: ${action}`);
};
return (
<View style={{ gap: 16, alignItems: 'center' }}>
<Popover>
<PopoverTrigger asChild>
<Button variant='outline'>βοΈ Options</Button>
</PopoverTrigger>
<PopoverContent
align='end'
style={{
padding: 8,
minWidth: 180,
}}
>
<MenuItem
icon='π€'
label='View Profile'
onPress={() => handleMenuAction('profile')}
/>
<MenuItem
icon='βοΈ'
label='Settings'
onPress={() => handleMenuAction('settings')}
/>
<MenuItem
icon='π'
label='Export Data'
onPress={() => handleMenuAction('export')}
/>
<View
style={{
height: 1,
backgroundColor: borderColor,
marginVertical: 4,
}}
/>
<MenuItem
icon='πͺ'
label='Sign Out'
onPress={() => handleMenuAction('signout')}
/>
<MenuItem
icon='ποΈ'
label='Delete Account'
onPress={() => handleMenuAction('delete')}
destructive
/>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button>βοΈ Actions</Button>
</PopoverTrigger>
<PopoverContent side='bottom' align='start' style={{ padding: 8 }}>
<MenuItem
icon='π'
label='Edit'
onPress={() => handleMenuAction('edit')}
/>
<MenuItem
icon='π'
label='Copy'
onPress={() => handleMenuAction('copy')}
/>
<MenuItem
icon='π€'
label='Share'
onPress={() => handleMenuAction('share')}
/>
<MenuItem
icon='β'
label='Add to Favorites'
onPress={() => handleMenuAction('favorite')}
/>
</PopoverContent>
</Popover>
</View>
);
}
API Reference
Popover
The root component that manages the popover state and provides context.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The popover trigger and content components. |
open | boolean | false | Controls the open state of the popover. |
onOpenChange | (open: boolean) => void | - | Callback fired when the open state changes. |
PopoverTrigger
The element that triggers the popover when pressed.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The trigger content. |
asChild | boolean | false | When true, merges props with the child element. |
style | ViewStyle | - | Additional styles to apply to the trigger container. |
PopoverContent
The container for the popover content with positioning logic.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The popover content. |
align | 'start' | 'center' | 'end' | center | How to align the popover relative to the trigger. |
side | 'top' | 'right' | 'bottom' | 'left' | bottom | The preferred side of the trigger to position against. |
sideOffset | number | 8 | The distance between the trigger and the popover. |
alignOffset | number | 0 | An offset in pixels from the alignment position. |
style | ViewStyle | - | Additional styles to apply to the content container. |
maxWidth | number | 300 | Maximum width of the popover in pixels. |
maxHeight | number | 400 | Maximum height of the popover in pixels. |
PopoverHeader
A header section for the popover content.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The header content. |
style | ViewStyle | - | Additional styles to apply to the header container. |
PopoverBody
The main content area of the popover.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The body content. |
style | ViewStyle | - | Additional styles to apply to the body container. |
PopoverFooter
A footer section for the popover content.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The footer content. |
style | ViewStyle | - | Additional styles to apply to the footer container. |
PopoverClose
A utility component that closes the popover when pressed.
Prop | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The close trigger content. |
asChild | boolean | false | When true, merges props with the child element. |
style | ViewStyle | - | Additional styles to apply to the close container. |
Positioning
The popover automatically positions itself relative to the trigger element and adjusts based on available screen space:
- Side: Controls which side of the trigger the popover appears on (
top
,right
,bottom
,left
) - Alignment: Controls how the popover aligns relative to the trigger (
start
,center
,end
) - Auto-flipping: When there's insufficient space, the popover automatically flips to the opposite side
- Boundary detection: Ensures the popover stays within screen boundaries with appropriate padding
Accessibility
The Popover component is built with accessibility in mind:
- Proper focus management when opening and closing
- Keyboard navigation support (Escape to close)
- Screen reader compatible with proper ARIA attributes
- Touch-friendly interaction areas
- Respects user's motion preferences