Skip to content

Lightbox

Fullscreen dialog for viewing image, video, or PDF media, with optional gallery navigation.

Forest path
Morning light through a forest path.
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={imageMedia[0]} />
Forest path
Morning light through a forest path.
1 / 3
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={imageMedia} />
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={videoMedia} />
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={pdfMedia} />
1 / 2
function LoadingStory(args: LightboxStoryArgs): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// Simulates resolving signed URLs after the lightbox opens.
useEffect(() => {
if (!isOpen) {
return;
}
const timeout = setTimeout(() => setIsLoading(false), 2000);
return () => clearTimeout(timeout);
}, [isOpen]);
const media: ReadonlyArray<LightboxMedia> = isLoading
? [
{alt: imageMedia[0].alt, isLoading: true},
{alt: pdfMedia.alt, isLoading: true, type: 'pdf'},
]
: [imageMedia[0], pdfMedia];
return (
<>
<Button
label="Open lightbox"
onClick={() => {
setIsLoading(true);
setIsOpen(true);
}}
/>
<Lightbox
{...args}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LoadingStory {...args} />
Forest path
Morning light through a forest path.
1 / 3
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={mixedMedia} />
Forest path
Morning light through a forest path.
1 / 3
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={imageMedia} />
Forest path
Morning light through a forest path.
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={imageMedia[0]} />
function LightboxStory({
defaultIndex,
media,
...args
}: LightboxStoryArgs & {media: LightboxProps['media']}): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button label="Open lightbox" onClick={() => setIsOpen(true)} />
<Lightbox
{...args}
defaultIndex={defaultIndex}
isOpen={isOpen}
media={media}
onOpenChange={setIsOpen}
/>
</>
);
}
<LightboxStory {...args} media={videoMedia} />
Mountain lake
A clear alpine lake below snow-covered mountains.
2 / 3
function ControlledIndexStory(args: LightboxStoryArgs): React.JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const [index, setIndex] = useState(1);
return (
<>
<div style={{display: 'flex', gap: 8}}>
<Button label="Open gallery" onClick={() => setIsOpen(true)} />
<Button label="Show first" onClick={() => setIndex(0)} />
<Button
label="Show last"
onClick={() => setIndex(imageMedia.length - 1)}
/>
</div>
<Lightbox
{...args}
index={index}
isOpen={isOpen}
media={imageMedia}
onIndexChange={setIndex}
onOpenChange={setIsOpen}
/>
</>
);
}
<ControlledIndexStory {...args} />
Forest path
Morning light through a forest path.
function HookStory(): React.JSX.Element {
const lightbox = useLightbox({media: imageMedia[0]});
return (
<>
<Button label="Open from hook" onClick={() => lightbox.open()} />
{lightbox.element}
</>
);
}
<HookStory />
Forest path
Mountain lake
Desert ridge
Forest path
Morning light through a forest path.
1 / 3
function HookGalleryStory(): React.JSX.Element {
const lightbox = useLightbox({media: imageMedia, hasZoom: true});
return (
<>
<div
style={{
display: 'grid',
gap: 12,
gridTemplateColumns: 'repeat(3, minmax(0, 140px))',
}}>
{imageMedia.map((item, index) => (
<div
key={item.src}
{...lightbox.getTriggerProps(index)}
style={{
border: 0,
borderRadius: 6,
cursor: 'pointer',
overflow: 'hidden',
}}>
<img
alt={item.alt}
src={item.src}
style={{
aspectRatio: '4 / 3',
display: 'block',
objectFit: 'cover',
width: '100%',
}}
/>
</div>
))}
</div>
{lightbox.element}
</>
);
}
<HookGalleryStory />

Fullscreen dialog for viewing image, video, or PDF media, with optional gallery navigation.

PropTypeDefaultDescription
classNamestring—Additional CSS class names applied to the dialog.
data-testidstring—Test ID applied to the dialog.
defaultIndexnumber0Initial gallery index for uncontrolled usage.
hasAutoPlaybooleanfalseWhether videos autoplay when opened.
hasZoombooleanfalseWhether images can be double-clicked to zoom.
indexnumber—Controlled gallery index.
isOpen*boolean—Whether the lightbox is open.
media*LightboxMedia | ReadonlyArray<LightboxMedia>—Media to display.
onIndexChange(index: number) => void—Called when gallery index changes.
onOpenChange*(isOpen: boolean) => void—Called when the lightbox requests an open-state change.
refRef<HTMLDialogElement>—Ref forwarded to the dialog element.
styleCSSProperties—Inline styles applied to the dialog.