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 treeimport { 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, onceimport { Lightbox } from './Lightbox'import { Gallery } from './Gallery'
export default function App() { return ( <> <Lightbox /> <Gallery /> </> )}The caller
anywhere in your app, imperativeimport { 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> )}