ImagePreview
A beautiful image gallery with zoom and swipe gestures
A full-featured image gallery component with pinch-to-zoom, swipe-to-navigate, and smooth animations. Perfect for displaying photos, products, and media galleries.
Overview
ImagePreview provides a native-like viewing experience for images. It handles gestures automatically and provides a polished, intuitive interface for image browsing.
Use it for:
- Photo galleries and albums
- Product image viewers
- Media previews
- Portfolio displays
- Document image viewing
Basic Usage
import { ImagePreview, Button } from 'prizmux'
import { useState } from 'react'
export default function App() {
const [visible, setVisible] = useState(false)
const images = [
'https://example.com/photo1.jpg',
'https://example.com/photo2.jpg',
'https://example.com/photo3.jpg',
]
return (
<>
<Button
title="View Gallery"
onPress={() => setVisible(true)}
/>
<ImagePreview
visible={visible}
images={images}
onClose={() => setVisible(false)}
/>
</>
)
}
import { ImagePreview, Button } from 'prizmux'
import { useState } from 'react'
export default function App() {
const [visible, setVisible] = useState(false)
const images = [
'https://example.com/photo1.jpg',
'https://example.com/photo2.jpg',
'https://example.com/photo3.jpg',
]
return (
<>
<Button
title="View Gallery"
onPress={() => setVisible(true)}
/>
<ImagePreview
visible={visible}
images={images}
onClose={() => setVisible(false)}
/>
</>
)
}
With Initial Index
import { ImagePreview } from 'prizmux'
<ImagePreview
visible={visible}
images={images}
initialIndex={2}
onClose={() => setVisible(false)}
/>
import { ImagePreview } from 'prizmux'
<ImagePreview
visible={visible}
images={images}
initialIndex={2}
onClose={() => setVisible(false)}
/>
Controlled Gallery
import { ImagePreview, Button } from 'prizmux'
import { View, Text } from 'react-native'
import { useState } from 'react'
export default function ControlledGallery() {
const [visible, setVisible] = useState(false)
const [currentIndex, setCurrentIndex] = useState(0)
const images = [
'https://example.com/photo1.jpg',
'https://example.com/photo2.jpg',
'https://example.com/photo3.jpg',
]
return (
<>
<Button
title="View Gallery"
onPress={() => setVisible(true)}
/>
<ImagePreview
visible={visible}
images={images}
initialIndex={currentIndex}
title={`${currentIndex + 1} of ${images.length}`}
onClose={() => setVisible(false)}
/>
</>
)
}
import { ImagePreview, Button } from 'prizmux'
import { View, Text } from 'react-native'
import { useState } from 'react'
export default function ControlledGallery() {
const [visible, setVisible] = useState(false)
const [currentIndex, setCurrentIndex] = useState(0)
const images = [
'https://example.com/photo1.jpg',
'https://example.com/photo2.jpg',
'https://example.com/photo3.jpg',
]
return (
<>
<Button
title="View Gallery"
onPress={() => setVisible(true)}
/>
<ImagePreview
visible={visible}
images={images}
initialIndex={currentIndex}
title={`${currentIndex + 1} of ${images.length}`}
onClose={() => setVisible(false)}
/>
</>
)
}
Product Image Gallery
import { ImagePreview, Button } from 'prizmux'
import { View, Image, ScrollView, Pressable } from 'react-native'
import { useState } from 'react'
export default function ProductGallery({ productImages }) {
const [visible, setVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(0)
return (
<View style={{ gap: 12 }}>
{/* Main image */}
<Pressable onPress={() => setVisible(true)}>
<Image
source={{ uri: productImages[selectedIndex] }}
style={{ width: '100%', height: 300, borderRadius: 12 }}
/>
</Pressable>
{/* Thumbnails */}
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{productImages.map((image, idx) => (
<Pressable
key={idx}
onPress={() => {
setSelectedIndex(idx)
setVisible(true)
}}
style={{
marginRight: 8,
opacity: selectedIndex === idx ? 1 : 0.6,
}}
>
<Image
source={{ uri: image }}
style={{ width: 80, height: 80, borderRadius: 8 }}
/>
</Pressable>
))}
</ScrollView>
{/* Gallery viewer */}
<ImagePreview
visible={visible}
images={productImages}
initialIndex={selectedIndex}
onClose={() => setVisible(false)}
/>
</View>
)
}
import { ImagePreview, Button } from 'prizmux'
import { View, Image, ScrollView, Pressable } from 'react-native'
import { useState } from 'react'
export default function ProductGallery({ productImages }) {
const [visible, setVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(0)
return (
<View style={{ gap: 12 }}>
{/* Main image */}
<Pressable onPress={() => setVisible(true)}>
<Image
source={{ uri: productImages[selectedIndex] }}
style={{ width: '100%', height: 300, borderRadius: 12 }}
/>
</Pressable>
{/* Thumbnails */}
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{productImages.map((image, idx) => (
<Pressable
key={idx}
onPress={() => {
setSelectedIndex(idx)
setVisible(true)
}}
style={{
marginRight: 8,
opacity: selectedIndex === idx ? 1 : 0.6,
}}
>
<Image
source={{ uri: image }}
style={{ width: 80, height: 80, borderRadius: 8 }}
/>
</Pressable>
))}
</ScrollView>
{/* Gallery viewer */}
<ImagePreview
visible={visible}
images={productImages}
initialIndex={selectedIndex}
onClose={() => setVisible(false)}
/>
</View>
)
}
Custom Icons
import { ImagePreview } from 'prizmux'
import { Text } from 'react-native'
<ImagePreview
visible={visible}
images={images}
onClose={() => setVisible(false)}
closeIcon={<Text style={{ fontSize: 24 }}>✕</Text>}
prevIcon={<Text style={{ fontSize: 24 }}>‹</Text>}
nextIcon={<Text style={{ fontSize: 24 }}>›</Text>}
/>
import { ImagePreview } from 'prizmux'
import { Text } from 'react-native'
<ImagePreview
visible={visible}
images={images}
onClose={() => setVisible(false)}
closeIcon={<Text style={{ fontSize: 24 }}>✕</Text>}
prevIcon={<Text style={{ fontSize: 24 }}>‹</Text>}
nextIcon={<Text style={{ fontSize: 24 }}>›</Text>}
/>
Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | false | Control gallery visibility |
images | string | string[] | Required | Image URLs to display |
initialIndex | number | 0 | Starting image index |
onClose | () => void | Required | Called when gallery closes |
title | string | - | Optional header title |
closeIcon | ReactNode | - | Custom close button icon |
prevIcon | ReactNode | - | Custom previous button icon |
nextIcon | ReactNode | - | Custom next button icon |
Features
- ✨ Pinch-to-zoom support
- 🎯 Swipe gestures for navigation
- 🎨 Smooth animations
- 📱 Responsive design
- 🎭 Customizable icons
- ♿ Accessibility support
Usage
Basic Gallery
import { ImagePreview } from 'prizmux'
export default function App() {
const images = [
{ uri: 'https://example.com/photo1.jpg' },
{ uri: 'https://example.com/photo2.jpg' },
{ uri: 'https://example.com/photo3.jpg' },
]
return <ImagePreview images={images} />
}
import { ImagePreview } from 'prizmux'
export default function App() {
const images = [
{ uri: 'https://example.com/photo1.jpg' },
{ uri: 'https://example.com/photo2.jpg' },
{ uri: 'https://example.com/photo3.jpg' },
]
return <ImagePreview images={images} />
}
With Initial Index
<ImagePreview
images={productImages}
initialIndex={2}
/>
<ImagePreview
images={productImages}
initialIndex={2}
/>
Controlled Gallery
import { ImagePreview, Button } from 'prizmux'
import { useState } from 'react'
import { View } from 'react-native'
export default function Gallery() {
const [currentIndex, setCurrentIndex] = useState(0)
const images = [
{ uri: 'https://example.com/photo1.jpg' },
{ uri: 'https://example.com/photo2.jpg' },
{ uri: 'https://example.com/photo3.jpg' },
]
return (
<View style={{ flex: 1 }}>
<ImagePreview
images={images}
initialIndex={currentIndex}
onIndexChange={setCurrentIndex}
/>
<View style={{ flexDirection: 'row', gap: 8 }}>
<Button
title="Previous"
disabled={currentIndex === 0}
onPress={() => setCurrentIndex(currentIndex - 1)}
/>
<Button
title="Next"
disabled={currentIndex === images.length - 1}
onPress={() => setCurrentIndex(currentIndex + 1)}
/>
</View>
</View>
)
}
import { ImagePreview, Button } from 'prizmux'
import { useState } from 'react'
import { View } from 'react-native'
export default function Gallery() {
const [currentIndex, setCurrentIndex] = useState(0)
const images = [
{ uri: 'https://example.com/photo1.jpg' },
{ uri: 'https://example.com/photo2.jpg' },
{ uri: 'https://example.com/photo3.jpg' },
]
return (
<View style={{ flex: 1 }}>
<ImagePreview
images={images}
initialIndex={currentIndex}
onIndexChange={setCurrentIndex}
/>
<View style={{ flexDirection: 'row', gap: 8 }}>
<Button
title="Previous"
disabled={currentIndex === 0}
onPress={() => setCurrentIndex(currentIndex - 1)}
/>
<Button
title="Next"
disabled={currentIndex === images.length - 1}
onPress={() => setCurrentIndex(currentIndex + 1)}
/>
</View>
</View>
)
}
Modal Gallery
import { ImagePreview, Button } from 'prizmux'
import { useState } from 'react'
import { View, Modal } from 'react-native'
export default function ModalGallery() {
const [visible, setVisible] = useState(false)
const images = [
{ uri: 'https://example.com/photo1.jpg' },
{ uri: 'https://example.com/photo2.jpg' },
]
return (
<>
<Button
title="View Gallery"
onPress={() => setVisible(true)}
/>
<Modal
visible={visible}
animationType="fade"
>
<View style={{ flex: 1, backgroundColor: '#000' }}>
<ImagePreview
images={images}
onClose={() => setVisible(false)}
/>
</View>
</Modal>
</>
)
}
import { ImagePreview, Button } from 'prizmux'
import { useState } from 'react'
import { View, Modal } from 'react-native'
export default function ModalGallery() {
const [visible, setVisible] = useState(false)
const images = [
{ uri: 'https://example.com/photo1.jpg' },
{ uri: 'https://example.com/photo2.jpg' },
]
return (
<>
<Button
title="View Gallery"
onPress={() => setVisible(true)}
/>
<Modal
visible={visible}
animationType="fade"
>
<View style={{ flex: 1, backgroundColor: '#000' }}>
<ImagePreview
images={images}
onClose={() => setVisible(false)}
/>
</View>
</Modal>
</>
)
}
Product Gallery
import { ImagePreview, Card, Text } from 'prizmux'
import { useState } from 'react'
import { View, Image, TouchableOpacity } from 'react-native'
export default function ProductGallery() {
const [selectedIndex, setSelectedIndex] = useState(0)
const images = product.images.map(url => ({ uri: url }))
return (
<View>
<ImagePreview
images={images}
initialIndex={selectedIndex}
onIndexChange={setSelectedIndex}
/>
<View style={{ flexDirection: 'row', gap: 8, marginTop: 12 }}>
{images.map((image, index) => (
<TouchableOpacity
key={index}
onPress={() => setSelectedIndex(index)}
style={{
borderWidth: index === selectedIndex ? 2 : 0,
borderRadius: 8,
overflow: 'hidden'
}}
>
<Image
source={image}
style={{ width: 60, height: 60 }}
/>
</TouchableOpacity>
))}
</View>
</View>
)
}
import { ImagePreview, Card, Text } from 'prizmux'
import { useState } from 'react'
import { View, Image, TouchableOpacity } from 'react-native'
export default function ProductGallery() {
const [selectedIndex, setSelectedIndex] = useState(0)
const images = product.images.map(url => ({ uri: url }))
return (
<View>
<ImagePreview
images={images}
initialIndex={selectedIndex}
onIndexChange={setSelectedIndex}
/>
<View style={{ flexDirection: 'row', gap: 8, marginTop: 12 }}>
{images.map((image, index) => (
<TouchableOpacity
key={index}
onPress={() => setSelectedIndex(index)}
style={{
borderWidth: index === selectedIndex ? 2 : 0,
borderRadius: 8,
overflow: 'hidden'
}}
>
<Image
source={image}
style={{ width: 60, height: 60 }}
/>
</TouchableOpacity>
))}
</View>
</View>
)
}
Gestures
- Swipe left/right — Navigate between images
- Pinch — Zoom in/out (when enabled)
- Double tap — Auto-zoom to content
- Pan while zoomed — Move around zoomed image
Notes
- Images are cached for smooth scrolling
- Zoom resets when swiping to next image
- Supports both local and remote images
- Page indicator shows current position
- Optimized for performance with large galleries