An image viewer for React (+ Native). It works with any image component - bring your own image component (BYOIC™)
https://github.com/user-attachments/assets/5062e949-b205-4260-830c-38041cec26db
The video makes Web look a bit weird, but don't worry. Here is why – it's just because of FlashList's Masonry List, which wraps the views with additional cells breaking z-index. But it isn't a fundamental Galeria issue.
<Image /> from react-native<SolitoImage /> from solito/image<Image /> from next/image<Image /> from expo-image<img /> on webFor iOS and Android, the implementation uses Swift (ImageViewer.swift) and Kotlin (imageviewer) respectively – see credits.
Web support is a simplified version of the native experience powered by Framer Motion. It currently supports a single image at a time.
import { Galeria } from '@nandorojo/galeria'
import { Image } from 'react-native' // works with ANY image component!
const url = 'https://my-image.com/image.jpg'
export const SingleImage = ({ style }) => (
<Galeria urls={[url]}>
<Galeria.Image>
<Image source={{ uri: url }} style={style} />
</Galeria.Image>
</Galeria>
)
Simply pass an array to urls.
import { Galeria } from '@nandorojo/galeria'
import { Image } from 'react-native' // works with ANY image component!
import localImage from './assets/local-image.png'
const urls = ['https://my-image.com/image.jpg', localImage]
export const MutliImage = ({ style }) => (
<Galeria urls={urls}>
{urls.map((url, index) => (
<Galeria.Image index={index} key={...}>
<Image source={typeof url === 'string' ? { uri: url } : url} style={style} />
</Galeria.Image>
))}
</Galeria>
)
import { Galeria } from '@nandorojo/galeria'
export const DarkMode = () => (
<Galeria urls={urls} theme="dark">
...
</Galeria>
)
import { Galeria } from '@nandorojo/galeria'
import { Image, type ImageAssetSource } from 'react-native' // works with ANY image component!
import { FlashList } from '@shopify/flash-list'
import localImage from './assets/local-image.png'
const urls = ['https://my-image.com/image.jpg', localImage]
const size = 100
export const FlashListSupport = () => {
return (
<Galeria urls={urls}>
<FlashList
data={urls}
renderItem={({ item, index }) => {
// you should put this in a memoized component
return (
<Galeria.Image index={index}>
<Image
source={src(item)}
style={{ width: size, height: size }}
/>
</Galeria.Image>
)
}}
numColumns={3}
estimatedItemSize={size}
keyExtractor={(item, i) => item + i}
/>
</Galeria>
)
}
const src = (s) => (typeof s === 'string' ? { uri: s } : s) // 🤷♂️
iOS & Android
To get the index of the currently shown image in the image viewer use onIndexChange. It triggers on initial open of the image viewer and when the user scrolls through the images.
<Galeria urls={urls}>
{urls.map((url, index) => (
<Galeria.Image
index={index} key={...}
onIndexChange={(e) => setCurrentIndex(e.nativeEvent.currentIndex)}
>
<Image source={typeof url === 'string' ? { uri: url } : url} style={style} />
</Galeria.Image>
))}
</Galeria>
iOS only
Hide the blur overlay that appears behind the image viewer.
<Galeria.Image hideBlurOverlay>
<Image source={{ uri: url }} style={style} />
</Galeria.Image>
iOS only
Hide the page indicator dots when viewing multiple images.
<Galeria.Image hidePageIndicators>
<Image source={{ uri: url }} style={style} />
</Galeria.Image>
Galeria does not use any React Native code on the web. It is a pure React component library.
So you can even use <img /> if you want to only use it on web.
import { Galeria } from '@nandorojo/galeria'
const urls = ['https://my-image.com/image.jpg']
export const WebSupport = () => (
<Galeria urls={urls}>
<Galeria.Image>
<img src={urls[0]} width={100} height={100} />
</Galeria.Image>
</Galeria>
)
import { SolitoImage } from 'solito/image'
const urls = ['https://my-image.com/image.jpg']
export const SolitoSupport = () => (
<Galeria urls={urls}>
<Galeria.Image>
<SolitoImage src={urls[0]} />
</Galeria.Image>
</Galeria>
)
'use client'
import { Galeria } from '@nandorojo/galeria'
import Image from 'next/image'
const urls = ['https://my-image.com/image.jpg']
export const NextJS = () => (
<Galeria urls={urls}>
<Galeria.Image>
<Image
src={urls[0]}
width={100}
height={100}
// edit these props for your use case
unoptimized
/>
</Galeria.Image>
</Galeria>
)
import { Galeria } from '@nandorojo/galeria'
import { Image } from 'expo-image'
const urls = ['https://my-image.com/image.jpg']
export const ExpoImage = () => (
<Galeria urls={urls}>
<Galeria.Image>
<Image source={urls[0]} style={{ width: 100, height: 100 }} />
</Galeria.Image>
</Galeria>
)
For iOS 16.4+ deployment target:
ios/Podfileexpo-build-propertiesExpo SDK 56 requires iOS 16.4+ for ExpoModulesCore.
yarn add @nandorojo/galeria
# or
npm i @nandorojo/galeria
Add @nandorojo/galeria to transpilePackages in your next.config.js.
module.exports = {
transpilePackages: ['@nandorojo/galeria'],
}
Galeria uses native libraries on iOS and Android, so it does not work with Expo Go. You will need to use a dev client.
After installing it, rebuild your native code:
npx expo prebuild
npx expo run:ios # or npx expo run:android
115 followers · starred May 2024
225 followers · starred May 2025
60 followers · starred Mar 2025
30 followers · starred Jun 2025
Swift
84.8%
TypeScript
11.1%
Kotlin
3.3%
An image viewer for React (+ Native). It works with any image component - bring your own image component (BYOIC™)
https://github.com/user-attachments/assets/5062e949-b205-4260-830c-38041cec26db
The video makes Web look a bit weird, but don't worry. Here is why – it's just because of FlashList's Masonry List, which wraps the views with additional cells breaking z-index. But it isn't a fundamental Galeria issue.
<Image /> from react-native<SolitoImage /> from solito/image<Image /> from next/image<Image /> from expo-image<img /> on webFor iOS and Android, the implementation uses Swift (ImageViewer.swift) and Kotlin (imageviewer) respectively – see credits.
Web support is a simplified version of the native experience powered by Framer Motion. It currently supports a single image at a time.
import { Galeria } from '@nandorojo/galeria'
import { Image } from 'react-native' // works with ANY image component!
const url = 'https://my-image.com/image.jpg'
export const SingleImage = ({ style }) => (
<Galeria urls={[url]}>
<Galeria.Image>
<Image source={{ uri: url }} style={style} />
</Galeria.Image>
</Galeria>
)
Simply pass an array to urls.
import { Galeria } from '@nandorojo/galeria'
import { Image } from 'react-native' // works with ANY image component!
import localImage from './assets/local-image.png'
const urls = ['https://my-image.com/image.jpg', localImage]
export const MutliImage = ({ style }) => (
<Galeria urls={urls}>
{urls.map((url, index) => (
<Galeria.Image index={index} key={...}>
<Image source={typeof url === 'string' ? { uri: url } : url} style={style} />
</Galeria.Image>
))}
</Galeria>
)
import { Galeria } from '@nandorojo/galeria'
export const DarkMode = () => (
<Galeria urls={urls} theme="dark">
...
</Galeria>
)
import { Galeria } from '@nandorojo/galeria'
import { Image, type ImageAssetSource } from 'react-native' // works with ANY image component!
import { FlashList } from '@shopify/flash-list'
import localImage from './assets/local-image.png'
const urls = ['https://my-image.com/image.jpg', localImage]
const size = 100
export const FlashListSupport = () => {
return (
<Galeria urls={urls}>
<FlashList
data={urls}
renderItem={({ item, index }) => {
// you should put this in a memoized component
return (
<Galeria.Image index={index}>
<Image
source={src(item)}
style={{ width: size, height: size }}
/>
</Galeria.Image>
)
}}
numColumns={3}
estimatedItemSize={size}
keyExtractor={(item, i) => item + i}
/>
</Galeria>
)
}
const src = (s) => (typeof s === 'string' ? { uri: s } : s) // 🤷♂️
iOS & Android
To get the index of the currently shown image in the image viewer use onIndexChange. It triggers on initial open of the image viewer and when the user scrolls through the images.
<Galeria urls={urls}>
{urls.map((url, index) => (
<Galeria.Image
index={index} key={...}
onIndexChange={(e) => setCurrentIndex(e.nativeEvent.currentIndex)}
>
<Image source={typeof url === 'string' ? { uri: url } : url} style={style} />
</Galeria.Image>
))}
</Galeria>
iOS only
Hide the blur overlay that appears behind the image viewer.
<Galeria.Image hideBlurOverlay>
<Image source={{ uri: url }} style={style} />
</Galeria.Image>
iOS only
Hide the page indicator dots when viewing multiple images.
<Galeria.Image hidePageIndicators>
<Image source={{ uri: url }} style={style} />
</Galeria.Image>
Galeria does not use any React Native code on the web. It is a pure React component library.
So you can even use <img /> if you want to only use it on web.
import { Galeria } from '@nandorojo/galeria'
const urls = ['https://my-image.com/image.jpg']
export const WebSupport = () => (
<Galeria urls={urls}>
<Galeria.Image>
<img src={urls[0]} width={100} height={100} />
</Galeria.Image>
</Galeria>
)
import { SolitoImage } from 'solito/image'
const urls = ['https://my-image.com/image.jpg']
export const SolitoSupport = () => (
<Galeria urls={urls}>
<Galeria.Image>
<SolitoImage src={urls[0]} />
</Galeria.Image>
</Galeria>
)
'use client'
import { Galeria } from '@nandorojo/galeria'
import Image from 'next/image'
const urls = ['https://my-image.com/image.jpg']
export const NextJS = () => (
<Galeria urls={urls}>
<Galeria.Image>
<Image
src={urls[0]}
width={100}
height={100}
// edit these props for your use case
unoptimized
/>
</Galeria.Image>
</Galeria>
)
import { Galeria } from '@nandorojo/galeria'
import { Image } from 'expo-image'
const urls = ['https://my-image.com/image.jpg']
export const ExpoImage = () => (
<Galeria urls={urls}>
<Galeria.Image>
<Image source={urls[0]} style={{ width: 100, height: 100 }} />
</Galeria.Image>
</Galeria>
)
For iOS 16.4+ deployment target:
ios/Podfileexpo-build-propertiesExpo SDK 56 requires iOS 16.4+ for ExpoModulesCore.
yarn add @nandorojo/galeria
# or
npm i @nandorojo/galeria
Add @nandorojo/galeria to transpilePackages in your next.config.js.
module.exports = {
transpilePackages: ['@nandorojo/galeria'],
}
Galeria uses native libraries on iOS and Android, so it does not work with Expo Go. You will need to use a dev client.
After installing it, rebuild your native code:
npx expo prebuild
npx expo run:ios # or npx expo run:android
115 followers · starred May 2024
225 followers · starred May 2025
60 followers · starred Mar 2025
30 followers · starred Jun 2025
Swift
84.8%
TypeScript
11.1%
Kotlin
3.3%