Documentation
React Photo Album allows you to build a responsive React photo gallery in minutes. To get started, follow the Installation and Minimal Setup Example guides, or feel free to explore the collection of examples with live demos.
Parameters marked with an asterisk () are required.
Prerequisites
React Photo Album provides four separate photo album components, depending on the layout you intend to use. Each component comes with its own CSS stylesheet.
Rows Layout
import { RowsPhotoAlbum } from "react-photo-album";
import "react-photo-album/rows.css";
Columns Layout
import { ColumnsPhotoAlbum } from "react-photo-album";
import "react-photo-album/columns.css";
Masonry Layout
import { MasonryPhotoAlbum } from "react-photo-album";
import "react-photo-album/masonry.css";
3-in-1
If you use more than one layout in our app, you may opt for the aggregate component, which bundles all three layouts.
import PhotoAlbum from "react-photo-album";
import "react-photo-album/styles.css";
Common Props
The following props are applicable to all three layouts.
Property | Type | Description |
---|---|---|
photos | Photo[] | An array of photos to display in the photo album. See Photo for details. |
spacing | number | function | Spacing between images (similar to CSS grid gap). Default responsive value:
|
padding | number | function | Padding around each image. Default value: 0 |
onClick | function | Photo click callback. See Click Handler for details. |
sizes | object | Photo album container width in various viewports. See Sizes for details. |
breakpoints | number[] | Photo album layout breakpoints. See Breakpoints for details. |
componentsProps | object | function | Additional HTML attributes to be passed to the rendered elements. See Components Props for details. |
render | object | function | Custom render functions. See Render Functions for details. |
defaultContainerWidth | number | The default container width in server-side rendering (SSR). See Default Container Width for details. |
skeleton | ReactNode | Fallback skeleton in SSR. See Skeleton for details. |
RowsPhotoAlbum Props
Property | Type | Description |
---|---|---|
targetRowHeight | number | function | Target row height. Default responsive value:
|
rowConstraints | object | function | Additional row constraints.
|
Usage example:
<RowsPhotoAlbum
photos={photos}
targetRowHeight={150}
rowConstraints={{ singleRowMaxHeight: 250 }}
/>
ColumnsPhotoAlbum Props
Property | Type | Description |
---|---|---|
columns | number | function | Number of columns. Default responsive value:
|
Usage example:
<ColumnsPhotoAlbum photos={photos} columns={4} />
MasonryPhotoAlbum Props
MasonryPhotoAlbum
accepts columns
prop identical to the one supported by the
ColumnsPhotoAlbum.
Usage example:
<MasonryPhotoAlbum photos={photos} columns={4} />
PhotoAlbum Props
The aggregate PhotoAlbum
component supports all relevant props that correspond
to the selected layout.
Property | Type | Description |
---|---|---|
layout | "columns" | "rows" | "masonry" | Photo album layout type. |
Usage example:
<PhotoAlbum layout="rows" photos={photos} targetRowHeight={150} />
Photo
Property | Type | Description |
---|---|---|
src | string | Image source. |
width | number | Image width in pixels. |
height | number | Image height in pixels. |
key | string | React key attribute. |
alt | string | Image alt attribute. |
title | string | Image title attribute. |
href | string | Image link URL. |
label | string | ARIA label for the link and button elements. |
srcSet | { | Optional array of alternative images to be included in the All images in a given |
You can also provide custom photo attributes and access them in the render functions.
Click Handler
You can add interactive behavior by providing the onClick
callback.
<RowsPhotoAlbum
photos={photos}
onClick={({ index }) => {
openLightbox(index);
}}
/>
The callback function accepts a single parameter with the following props:
Property | Type | Description |
---|---|---|
index | number | Photo index in the original photos array. |
photo | Photo | Photo object. |
event | MouseEvent | Corresponding mouse event. |
Responsive Props
React Photo Album accepts various props as responsive parameters.
Responsive props can be provided either as a hard-coded value:
<ColumnsPhotoAlbum photos={photos} columns={3} />
or as a function of container width:
<ColumnsPhotoAlbum
photos={photos}
columns={(containerWidth) => {
if (containerWidth < 400) return 2;
if (containerWidth < 800) return 3;
return 4;
}}
/>
Components Props
You can pass additional HTML attributes to the rendered elements through the
componentsProps
parameter.
Property | Type | Description |
---|---|---|
container | ComponentProps<"div"> | Additional HTML attributes for the outer div container. |
track | ComponentProps<"div"> | Additional HTML attributes for the row / column div containers. |
wrapper | ComponentProps<"div"> | Additional HTML attributes for the image div wrapper. |
link | ComponentProps<"a"> | Additional HTML attributes for the photo a link. |
button | ComponentProps<"button"> | Additional HTML attributes for the photo button element. |
image | ComponentProps<"img"> | Additional HTML attributes for the photo Default: { loading: "lazy", decoding: "async"} |
Usage example:
<RowsPhotoAlbum
photos={photos}
componentsProps={(containerWidth) => ({
image: { loading: (containerWidth || 0) > 600 ? "eager" : "lazy" },
})}
/>
Render Functions
React Photo Album allows you to customize all rendered elements by
supplying your own custom render functions. Each render function provides the
default element's props as a first parameter. These typically include style
and className
attributes the default implementation requires.
Property | Type | Description |
---|---|---|
container | (props) => ReactNode | Render custom container. See Container for details. |
track | (props) => ReactNode | Render custom row / column container. See Track for details. |
wrapper | (props, context) => ReactNode | Render custom image wrapper. See Wrapper for details. |
link | (props, context) => ReactNode | Render custom link element. See Link for details. |
button | (props, context) => ReactNode | Render custom button element. See Button for details. |
image | (props, context) => ReactNode | Render custom image element. See Image for details. |
extras | (props, context) => ReactNode | Render custom markup immediately after each image. See Extras for details. |
photo | (props, context) => ReactNode | Render custom photo. See Photo for details. |
When applicable, the second parameter represents the photo rendering context.
Property | Type | Description |
---|---|---|
photo | Photo | Photo object. |
index | number | Photo index in the original photos array. |
width | number | Rendered photo width in pixels. |
height | number | Rendered photo height in pixels. |
Container
You can customize the photo album div
container through the render.container
prop. Your implementation must forward ref
attribute to the underlying
container element.
<RowsPhotoAlbum
photos={photos}
render={{
container: ({ ref, ...rest }) => <div ref={ref} {...rest} />,
}}
/>
Track
You can customize the row / column div
containers through the render.track
prop. This is not common.
<RowsPhotoAlbum
photos={photos}
render={{
track: (props) => <div {...props} />,
}}
/>
Wrapper
You can customize the image wrapper through the render.wrapper
prop. This
wrapper is rendered only when photos are not clickable (there is no href
photo
prop and no onClick
callback). This is not common.
<RowsPhotoAlbum
photos={photos}
render={{
wrapper: (props) => <div {...props} />,
}}
/>
Link
Link element is rendered when a photo has an href
attribute. You can provide
your own link implementation through the render.link
prop.
<RowsPhotoAlbum
photos={photos}
render={{
link: (props) => <a {...props} />,
}}
/>
Button
Button element is rendered when the photo album has an onClick
callback. You
can provide your own button implementation through the render.button
prop.
<RowsPhotoAlbum
photos={photos}
render={{
button: (props) => <button {...props} />,
}}
/>
Image
You can provide your own image implementation through the render.image
prop.
<RowsPhotoAlbum
photos={photos}
render={{
image: (props) => <img {...props} />,
}}
/>
Extras
You can render custom elements alongside each photo. This can be useful for
rendering interactive icons with position: absolute
.
<RowsPhotoAlbum
photos={photos}
render={{
extras: (_, { photo, index }) => (
<FavoriteIcon photo={photo} index={index} />
),
}}
/>
Photo
This is the render function that completely overrides the default wrapper
,
link
, button
, image
and extras
render functions. The only prop provided
in the first argument, is the onClick
callback.
<RowsPhotoAlbum
photos={photos}
render={{
photo: ({ onClick }, { photo, width, height }) => (
<CustomPhoto
photo={photo}
width={width}
height={height}
onClick={onClick}
/>
),
}}
/>
Breakpoints
By default, React Photo Album re-calculates its layout every time its
container width changes. For example, the layout may be re-calculated dozens of
times during a browser window resize. If this behavior is undesired, you can
avoid it by providing the breakpoints
prop (e.g.,
[300, 600, 1200]). When the breakpoints
parameter is defined, React Photo Album calculates the layout only once per
the breakpoint interval.
<RowsPhotoAlbum photos={photos} breakpoints={[300, 600, 1200]} />
Sizes
Photo album components automatically generate sizes
and srcset
image
attributes when photo objects contain srcSet
array. By default,
React Photo Album assumes that the photo album utilizes approximately
100vw of the page width. If that's not the case,
you can improve the performance of your responsive images by describing your
photo album size in different viewports.
For example, this website uses the following sizes
attribute to account for
the content container padding and the left-hand side navigation menu:
<RowsPhotoAlbum
photos={photos}
sizes={{
size: "992px",
sizes: [
{ viewport: "(max-width: 767px)", size: "calc(100vw - 32px)" },
{ viewport: "(max-width: 1279px)", size: "calc(100vw - 288px)" },
],
}}
/>
Styling Focus Indicators
React Photo Album does not provide an opinionated styling for keyboard focus indicators, so buttons and links are styled with browser-default focus indicator styles. You can implement your own styles to match your website design. Here is an example you can use as a starting point.
.react-photo-album--link:focus-visible,
.react-photo-album--button:focus-visible {
outline: 9px double white;
box-shadow: 0 0 0 6px black;
}
@supports not selector(:focus-visible) {
.react-photo-album--link:focus,
.react-photo-album--button:focus {
outline: 9px double white;
box-shadow: 0 0 0 6px black;
}
}
Infinite Scroll
You can use the experimental InfiniteScroll
component to implement an infinite
scroll feature in your app. The component is currently exported as
UnstableInfiniteScroll
. Please share your feedback if you have successfully
used this component in your project or encountered any issues.
import { UnstableInfiniteScroll as InfiniteScroll } from "react-photo-album/scroll";
Property | Type | Description |
---|---|---|
children | ReactElement | The photo album component, which must be the only child. |
fetch | (index: number) => Promise<Photo[] | null> | Photo fetcher. Resolve the promise with |
photos | Photo[] | Initial photos (optional). |
onClick | ({ photos, photo, index, event }) => void | Photo click callback. The |
retries | number | Retry attempts. Default value: 0 |
singleton | boolean | Use a single photo album component (masonry layout). |
fetchRootMargin | string | Fetcher Default value: 800px |
offscreenRootMargin | string | Offscreen Default value: 2000px |
error | ReactNode | Markup to display when an error occurred. |
loading | ReactNode | Markup to display while fetching additional photos. |
finished | ReactNode | Markup to display when no more photos are available. |
Rows Layout With Infinite Scroll
import { RowsPhotoAlbum } from "react-photo-album";
import { UnstableInfiniteScroll as InfiniteScroll } from "react-photo-album/scroll";
import "react-photo-album/rows.css";
// ...
export default function Gallery() {
return (
<InfiniteScroll photos={initialPhotos} fetch={fetchPhotos}>
<RowsPhotoAlbum
photos={[]}
spacing={20}
componentsProps={{ container: { style: { marginBottom: 20 } } }}
/>
</InfiniteScroll>
);
}
Masonry Layout With Infinite Scroll
import { MasonryPhotoAlbum } from "react-photo-album";
import { UnstableInfiniteScroll as InfiniteScroll } from "react-photo-album/scroll";
import "react-photo-album/masonry.css";
// ...
export default function Gallery() {
return (
<InfiniteScroll singleton photos={initialPhotos} fetch={fetchPhotos}>
<MasonryPhotoAlbum
photos={[]}
spacing={20}
componentsProps={{ container: { style: { marginBottom: 20 } } }}
/>
</InfiniteScroll>
);
}
Columns Layout With Infinite Scroll
Columns layout is not a good fit for the infinite scroll feature.
Server-Side Rendering (SSR)
By default, React Photo Album produces an empty markup in SSR because the
actual container width is usually unknown during server-side rendering. This
default behavior causes content layout shift after hydration. As a workaround,
you can specify the defaultContainerWidth
prop to enable photo album markup
rendering in SSR. However, that will likely result in the photo album layout
shift once the photo album re-calculates its layout on the client. With this
being said, there isn't a perfect solution for SSR, but there are several
options to choose from, depending on your use case.
Default Container Width
To render photo album markup on the server, you can specify the
defaultContainerWidth
value. It is a perfect SSR solution if your photo album
has a constant width in all viewports (e.g., an image picker in a fixed-size
sidebar). However, if the client-side photo album width doesn't match the
defaultContainerWidth
, you are almost guaranteed to see a layout shift after
hydration.
<RowsPhotoAlbum photos={photos} defaultContainerWidth={800} />
Skeleton
Alternatively, you can provide a fallback skeleton in the skeleton
prop that
will be rendered in SSR and swapped with the actual photo album markup after
hydration. This approach allows you to reserve a blank space on the page for the
photo album markup and avoid a flash of the below-the-fold content during
hydration. The downside of this approach is that images don't start downloading
until after hydration unless you manually add prefetch links to the document
<head>
.
<RowsPhotoAlbum
photos={photos}
skeleton={<div style={{ width: "100%", minHeight: 800 }} />}
/>
Visibility Hidden
Another option is to render the photo album on the server with
visibility: hidden
. This way, you can avoid a flash of the below-the-fold
content and allow the browser to start downloading images before hydration.
<RowsPhotoAlbum
photos={photos}
defaultContainerWidth={800}
componentsProps={(containerWidth) =>
containerWidth === undefined
? {
container: { style: { visibility: "hidden" } },
}
: {}
}
/>
SSR Component
The ultimate zero-CLS solution requires pre-rendering multiple layouts on the
server and displaying the correct one on the client using CSS @container
queries. React Photo Album provides an experimental SSR
component
implementing this approach (the component is currently exported as
UnstableSSR
). The downside of this approach is the overhead in SSR-generated
markup and the hydration of multiple photo album instances on the client (which
may be a reasonable compromise if zero CLS is a must-have requirement). You can
find a live demo in the Zero CLS SSR example.
import { RowsPhotoAlbum } from "react-photo-album";
import { UnstableSSR as SSR } from "react-photo-album/ssr";
import "react-photo-album/rows.css";
import photos from "./photos";
export default function Gallery() {
return (
<SSR breakpoints={[300, 600, 900, 1200]}>
<RowsPhotoAlbum photos={photos} />
</SSR>
);
}
Property | Type | Description |
---|---|---|
breakpoints | number[] | Photo album layout breakpoints. |
children | ReactElement | Photo album instance, which must be the only child. |
unstyled | boolean | If |
classNames | { | Custom class names for the container and the breakpoint intervals. |
Please share your feedback if you have successfully used this component in your project or encountered any issues.
Server Component
React Photo Album provides an experimental server component for rendering
static photo albums on the server with zero client-side JS bundle (the component
produces pure HTML markup with no client components). The component is currently
exported as UnstableServerPhotoAlbum
. You can find a live demo in the
Server Component example.
import { UnstableServerPhotoAlbum as ServerPhotoAlbum } from "react-photo-album/server";
Property | Type | Description |
---|---|---|
layout | "rows" | "columns" | "masonry" | Layout type. |
breakpoints | number[] | Photo album layout breakpoints. |
unstyled | boolean | If true , do not include the inline stylesheet. |
classNames | { | Custom class names for the container and the breakpoint intervals. |
In addition to the props listed above, ServerPhotoAlbum
supports all relevant
props corresponding to the selected layout type except the
defaultContainerWidth
, onClick
and skeleton
.
Server Component With Default Styling
import { UnstableServerPhotoAlbum as ServerPhotoAlbum } from "react-photo-album/server";
import "react-photo-album/rows.css";
// ...
export default function Gallery() {
return (
<ServerPhotoAlbum
layout="rows"
photos={photos}
breakpoints={[300, 600, 900]}
/>
);
}
Server Component With Tailwind CSS Styling
Here is an example of custom styling using @tailwindcss/container-queries.
import { UnstableServerPhotoAlbum as ServerPhotoAlbum } from "react-photo-album/server";
import "react-photo-album/rows.css";
// ...
export default function Gallery() {
return (
<ServerPhotoAlbum
unstyled
layout="rows"
photos={photos}
breakpoints={[300, 600, 900]}
classNames={{
container: "@container",
breakpoints: {
150: "block @[300px]:hidden",
300: "hidden @[300px]:block @[600px]:hidden",
600: "hidden @[600px]:block @[900px]:hidden",
900: "hidden @[900px]:block",
},
}}
/>
);
}
Please share your feedback if you have successfully used this component in your project or encountered any issues.
Previous Versions
Are you looking for documentation for one of the previous versions?