react-photoswipe-gallery: a React wrapper that keeps PhotoSwipe in charge
🏙 React component wrapper around PhotoSwipe
At a glance
- What is it?
- react-photoswipe-gallery wraps the PhotoSwipe lightbox in two React components, a Gallery and an Item, and deliberately does very little: you render the thumbnails yourself, hand the library the original image, and it takes care of the lightbox, the hash navigation, the captions, the plugins and the custom UI elements. It is MIT licensed and on version 4.x, which targets PhotoSwipe v5.
- Who is it for?
- react-photoswipe-gallery suits a React project that already uses PhotoSwipe v5 and wants the lightbox to stay the library's job rather than reimplementing one, and it suits a team that wants thumbnails rendered by its own components while the lightbox is handled for them.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two components and a stylesheet
react-photoswipe-gallery is a configurable and flexible React component wrapper around PhotoSwipe, and the shape of it is small.
There is a Gallery and an Item. The Gallery is the lightbox container and the Item is one slide. The thumbnail is yours to render: the Item gives you a ref and an open callback, and you attach them to whatever markup you want. That is the design decision that makes the wrapper pleasant to use, because a gallery on a product page usually has to look like the rest of the page, and a component that renders its own image markup fights you.
The basic usage shows the whole API in one block:
import 'photoswipe/dist/photoswipe.css'
import { Gallery, Item } from 'react-photoswipe-gallery'
const MyGallery = () => (
<Gallery>
<Item
original="https://placekitten.com/1024/768?image=1"
thumbnail="https://placekitten.com/80/60?image=1"
width="1024"
height="768"
>
{({ ref, open }) => (
<img ref={ref} onClick={open} src="https://placekitten.com/80/60?image=1" />
)}
</Item>
<Item
original="https://placekitten.com/1024/768?image=2"
thumbnail="https://placekitten.com/80/60?image=2"
width="1024"
height="768"
>
{({ ref, open }) => (
<img ref={ref} onClick={open} src="https://placekitten.com/80/60?image=2" />
)}
</Item>
</Gallery>
)Note that PhotoSwipe's own stylesheet is imported separately. The wrapper does not bundle the CSS, which is the right call for a library, since the theme is PhotoSwipe's to define and you may already be loading it.
The installation is the usual two packages, and you need PhotoSwipe itself as a peer you install alongside:
yarn add photoswipe react-photoswipe-gallerynpm install photoswipe react-photoswipe-gallery --saveThe version pairing is stated at the top of the readme and it matters. The current version of react-photoswipe-gallery is compatible with PhotoSwipe v5, and if you need PhotoSwipe v4 you should use react-photoswipe-gallery v1 instead.
Hash navigation needs a Gallery id, and captions need two props
The two small features that every real gallery needs are handled by props rather than by configuration objects, which is the API style in evidence throughout this wrapper.
For hash navigation you should pass a unique id prop to the Gallery component. Optionally you can also pass the id to the Item component, and otherwise the index is used:
const MyGallery = () => (
<Gallery id="my-gallery">
<Item
id="first-pic"
{/*...*/}
/>
<Item
id="second-pic"
{/*...*/}
/>
</Gallery>
)The index fallback is the detail to be aware of. Without an Item id, the fragment in the URL is the position in the list, so if you reorder or filter the items the deep link will point at a different picture. Giving each Item a stable id costs one prop and makes the links durable.
Captions work the same way. If you want captions on your slides you need to pass the withCaption prop to the Gallery and the caption prop to each Item, and if a caption is not provided it will use the alt prop:
const MyGallery = () => (
<Gallery withCaption>
<Item
caption="Foo"
{/*...*/}
/>
<Item
alt="Bar"
{/*...*/}
/>
</Gallery>
)The alt fallback is the useful part. It means an image with meaningful alternative text gets a caption for free, and an image with a real caption prop gets that instead, without the two having to be kept in sync by hand.
Plugins and custom UI elements pass the lightbox through
The wrapper does not hide PhotoSwipe from you when you need its extensions, and the two mechanisms are separate.
For plugins, there is a plugins prop that accepts the function in which you should register all of your plugins, providing pswpLightbox to the plugin constructor. The example uses the photoswipe-dynamic-caption-plugin:
import 'photoswipe-dynamic-caption-plugin/photoswipe-dynamic-caption-plugin.css'
import PhotoSwipeDynamicCaption from 'photoswipe-dynamic-caption-plugin'
const MyGallery = () => (
<Gallery
plugins={(pswpLightbox) => {
// register plugin
const captionPlugin = new PhotoSwipeDynamicCaption(pswpLightbox, {
captionContent: (slide) => slide.data.alt,
})
// register another plugin
// ...
}}
>
{/*...*/}
</Gallery>
)The pattern is the PhotoSwipe one, with the lightbox instance handed to you so you can construct the plugin exactly as its own documentation says. That is what makes the wrapper compatible with the ecosystem of PhotoSwipe plugins rather than only with PhotoSwipe itself.
For custom UI elements, there is a uiElements prop that accepts an array of configuration objects for custom UI elements, and each one carries a name, an ariaLabel, an order, an isButton flag, an html description and an appendTo target, along with onInit and onClick handlers:
const uiElements = [
// add custom UI element
{
name: 'custom-button',
ariaLabel: 'Custom button',
order: 9,
isButton: true,
html: {
isCustomSVG: true,
inner:
'<path d="<ICON_PATH>" id="pswp__icn-cstm-btn"/>',
outlineID: 'pswp__icn-cstm-btn',
},
appendTo: 'bar',
onInit: (el, pswpInstance) => {
// do something on UI element's init event
},
onClick: (e, el, pswpInstance) => {
// do something on UI element's click event
},
},
// add another custom UI element
// ...
]
const MyGallery = () => (
<Gallery uiElements={uiElements}>
{/*...*/}
</Gallery>
)The configuration is a direct pass-through of PhotoSwipe's own UI element format, which is the point. A wrapper that renamed these fields would have you translating between the two libraries' documentation every time you looked something up.
dataSource is the escape hatch for larger galleries
There are two ways to feed slides into the lightbox, and the second one is the one that scales.
The first is the Item children pattern shown above, where each slide is a child of the Gallery. The second is the dataSource prop, which passes slides data to Photoswipe directly:
const dataSource = [
{
sourceId: 1, // needed to connect following data with Item component
original: "https://placekitten.com/1024/768?image=1",
thumbnail: "https://placekitten.com/80/60?image=1",
width: "1024",
height: "768",
},
{
sourceId: 2,
...
},
{
sourceId: 3,
...
},
]
const MyGallery = () => (
<Gallery dataSource={dataSource}>
<Item
sourceId={1} // needed to connect Item component with data from dataSource
>
{({ ref, open }) => (
<button type="button" ref={ref} onClick={open}>
Open gallery at first slide
</button>
)}
</Item>
</Gallery>
)The sourceId field is the join key. The comment in the source says it is needed to connect the following data with the Item component, and the reverse, connecting an Item component with data from dataSource. So you can have a handful of visible thumbnails on the page and a large dataSource behind them, and open the lightbox at an arbitrary slide rather than at the first one.
That is the use case the readme calls out: if you need to render only some part of the images as thumbnails and show all available images in Photoswipe. A product page with a grid of ten products and a lightbox that can browse all fifty images is exactly the situation where the children pattern gets awkward.
Custom slide content works alongside either pattern, through the content and html props on the Item, where content takes a React element and html takes a string.
The documentation for all of this is a Storybook, published at dromru.github.io/react-photoswipe-gallery, and the stories are written as real world examples so that you can see them at the bottom of the Storybook UI in the Story tab. Each feature in the readme links to the corresponding story file, so the source code of every example is a click away.
A small package with a conventional toolchain
The repository is a small, well-organised package, and the tooling tells you it is maintained with care rather than abandoned.
The package is react-photoswipe-gallery at version 4.1.2, published as an ES module with dist/index.js as the main entry and dist/index.d.ts as the types, and the exports map points at those two files. The published files list covers the dist directory and the src directory, excluding the tests and the storybook, so the source ships alongside the build.
The build is a two step TypeScript process. The build-ts script clears the dist directory and runs tsc against tsconfig.build.json, and the patch-import step then runs babel over the output directory with a plugin that adds the import extensions, because the source is written as native ESM and Node needs the file extensions spelled out in the emitted imports.
The scripts around it are conventional and tell you what the project expects of a contributor. There is jest for tests, a separate type-check script running tsc with no emit, eslint for linting with a lint-all variant that globs over js, jsx, ts and tsx, prettier for formatting, and a commit script using git-cz. Releases are cut with standard-version, and the prepare script installs the husky hooks when husky is present.
The repository root shows the supporting infrastructure. There is a .storybook/ directory, a .husky/ directory, a .lintstagedrc for staged file linting, a commitlint.config.cjs enforcing conventional commit messages, and a renovate.json for dependency updates. The CI storybook scripts run on port 6006, and the Storybook packages themselves are 7.6.x, matching the version of the other tooling in the dev dependencies.
The release history is short and recent, which is a good sign for a component library. The three most recent tags are v4.1.2 from 2026-06-15, v4.1.1 from 2026-06-14 and v4.1.0 from 2026-06-11, three patch releases in five days, and the repository is not archived with the last push on 2026-10-20, about a day before the current date. There is a CHANGELOG.md at the root, and the licence is MIT with a LICENSE file.
Editorial conclusion
react-photoswipe-gallery suits a React project that already uses PhotoSwipe v5 and wants the lightbox to stay the library's job rather than reimplementing one, and it suits a team that wants thumbnails rendered by its own components while the lightbox is handled for them. It does not suit a project still on PhotoSwipe v4, because the readme points those users at the older v1 line of this wrapper, and it does not suit anyone who wants a gallery component that renders its own images. Before installing, check the PhotoSwipe major version you need, decide between the Item children pattern and the dataSource prop, and set a Gallery id if you want the URL to reflect the open slide.
Frequently asked questions
How do I install react-photoswipe-gallery?
Install both packages, since PhotoSwipe is a peer rather than a bundled dependency: yarn add photoswipe react-photoswipe-gallery, or npm install photoswipe react-photoswipe-gallery --save. You also need to import PhotoSwipe's own stylesheet, photoswipe/dist/photoswipe.css, which the wrapper does not include.
Which version of PhotoSwipe does react-photoswipe-gallery support?
The current version of react-photoswipe-gallery is compatible with PhotoSwipe v5. If you need PhotoSwipe v4, the readme directs you to use react-photoswipe-gallery v1 instead. The package is on version 4.1.2.
How do I enable hash navigation in react-photoswipe-gallery?
Pass a unique id prop to the Gallery component. You can also pass an id to the Item component, and otherwise the index is used, so giving each Item a stable id makes the deep links survive reordering.
What is the dataSource prop for in react-photoswipe-gallery?
It passes slides data to Photoswipe directly instead of using Item children, with a sourceId field on each entry to connect the data to the Item components. It is useful when you want to render only some of the images as thumbnails while showing all available images in the lightbox.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dromru-react-photoswipe-gallery)Was this useful?