react-call v2

Image lightbox

Click a thumbnail, open the full image as an overlay. The Callable closes on backdrop click or Escape.

click a thumbnail → lightbox

The Callable

declared once, mounted in the React tree
Lightbox.tsx
import { useEffect } from 'react'
import { createCallable } from 'react-call'
interface Props {
src: string
alt: string
}
export const Lightbox = createCallable<Props, void>(({ call, src, alt }) => {
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') call.end()
}
document.addEventListener('keydown', onKey)
return () => document.removeEventListener('keydown', onKey)
}, [call])
return (
<div
role="dialog"
aria-modal="true"
aria-label={alt}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-6"
onClick={() => call.end()}
onKeyDown={(e) => e.stopPropagation()}
>
<img
src={src}
alt={alt}
className="max-h-full max-w-full rounded-md shadow-2xl"
/>
<button
type="button"
onClick={() => call.end()}
aria-label="Close"
className="absolute right-4 top-4 rounded-full bg-white/10 px-3 py-1 text-white backdrop-blur transition-colors hover:bg-white/20"
>
×
</button>
</div>
)
})
Lightbox.displayName = 'Lightbox'

The Root

mounted in your app tree, once
App.tsx
import { Lightbox } from './Lightbox'
import { Gallery } from './Gallery'
export default function App() {
return (
<>
<Lightbox />
<Gallery />
</>
)
}

The caller

anywhere in your app, imperative
Gallery.tsx
import { Lightbox } from './Lightbox'
const THUMBNAILS = [
{
thumb:
'https://images.unsplash.com/photo-1518770660439-4636190af475?w=160&h=120&fit=crop',
full: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=1200',
alt: 'Circuit board macro',
},
{
thumb:
'https://images.unsplash.com/photo-1497436072909-60f360e1d4b1?w=160&h=120&fit=crop',
full: 'https://images.unsplash.com/photo-1497436072909-60f360e1d4b1?w=1200',
alt: 'Forest path',
},
{
thumb:
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=160&h=120&fit=crop',
full: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200',
alt: 'Mountains at dusk',
},
]
export const Gallery = () => {
const handleClick = (src: string, alt: string) => () =>
Lightbox.call({ src, alt })
return (
<div className="flex flex-col items-center gap-3">
<div className="flex gap-2">
{THUMBNAILS.map((t) => (
<button
key={t.thumb}
type="button"
onClick={handleClick(t.full, t.alt)}
className="overflow-hidden rounded-md border border-[var(--color-border)] transition-transform hover:scale-105"
>
<img src={t.thumb} alt={t.alt} className="h-20 w-28 object-cover" />
</button>
))}
</div>
<span className="font-mono text-xs text-[var(--color-fg-subtle)]">
click a thumbnail → lightbox
</span>
</div>
)
}