Lightbox

Uma galeria de imagens e vídeos em tela cheia, com @reelkit/react-lightbox.

Ver demonstração ao vivo →

Recursos

Imagens e vídeo
Slides de vídeo já incluídos
Gestos de toque
Deslize para navegar
Deslizar para fechar
Deslize para cima e dispense
Navegação por teclado
Setas e Escape
Tela cheia
API entre navegadores
Transições
Deslizar, esmaecer, virar, aproximar
Pré-carregamento
Imagens vizinhas buscadas antes
Botão de som
Mudo por slide
Estados de carregamento
Girador e renderização própria
Tratamento de erros
Ícone de erro e renderização própria
Render Props
6 zonas personalizáveis
Hooks
useVideoSlideRenderer e useFullscreen
Estado na URL
Links para compartilhar e favoritar
Apresentação
Avanço automático opcional, com play/pause
Download
Botão opcional que salva o item ativo

Instalação

bash
npm install @reelkit/react-lightbox @reelkit/react lucide-react

Não esqueça de importar os estilos:

typescript
import '@reelkit/react-lightbox/styles.css';
Ícones

Os controles padrão usam lucide-react para os ícones. Se você prefere outra biblioteca de ícones, use renderControls e renderNavigation para fornecer os seus.

Início rápido

O componente LightboxOverlay mostra imagens em tela cheia. Passe um array de objetos LightboxItem e controle a visibilidade com um índice que aceita nulo.

tsx
import { useState } from 'react';
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: 'https://example.com/image1.jpg',
    title: 'Sunset',
    description: 'Beautiful sunset over the ocean',
  },
  {
    src: 'https://example.com/image2.jpg',
    title: 'Mountains',
  },
];

function App() {
  const [index, setIndex] = useState<number | null>(null);

  return (
    <>
      {images.map((img, i) => (
        <img
          key={i}
          src={img.src}
          onClick={() => setIndex(i)}
        />
      ))}
      <LightboxOverlay
        isOpen={index !== null}
        images={images}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
      />
    </>
  );
}
LightboxPage.tsx
import { useState } from 'react';
import {
  LightboxOverlay,
  flipTransition,
  lightboxFadeTransition,
  lightboxZoomTransition,
  slideTransition,
  type LightboxItem,
} from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';
import '@reelkit/react-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: '/cdn/samples/images/image-01.jpg',
    title: 'Mountain River',
    description: 'A beautiful mountain river flowing through the forest',
    width: 1600,
    height: 1000,
  },
  {
    src: '/cdn/samples/images/image-02.jpg',
    title: 'Snowy Peaks',
    description: 'Majestic snow-capped mountains reaching for the sky',
    width: 1000,
    height: 1600,
  },
  {
    src: '/cdn/samples/images/image-03.jpg',
    title: 'Foggy Forest',
    description: 'Misty morning in the dense forest',
    width: 1600,
    height: 900,
  },
  {
    src: '/cdn/samples/images/image-04.jpg',
    title: 'Ocean Waves',
    description: 'Powerful ocean waves crashing against the rocky shore',
    width: 900,
    height: 1400,
  },
  {
    src: '/cdn/samples/images/image-05.jpg',
    title: 'Autumn Path',
    description: 'A winding path through the autumn forest',
    width: 1600,
    height: 1067,
  },
  {
    src: '/cdn/samples/images/image-06.jpg',
    title: 'Coastal Cliffs',
    description: 'Dramatic coastal cliffs overlooking the deep blue sea',
    width: 1600,
    height: 1067,
  },
];

const transitions: { label: string; fn: TransitionTransformFn }[] = [
  { label: 'slide', fn: slideTransition },
  { label: 'fade', fn: lightboxFadeTransition },
  { label: 'flip', fn: flipTransition },
  { label: 'zoom-in', fn: lightboxZoomTransition },
];

export default function App() {
  const [index, setIndex] = useState<number | null>(null);
  const [transitionFn, setTransitionFn] = useState<TransitionTransformFn>(
    () => slideTransition,
  );

  return (
    <div style={{ padding: 16, background: '#f8fafc', minHeight: '100vh' }}>
      {/* Transition picker */}
      <div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>
        {transitions.map((t) => (
          <button
            key={t.label}
            onClick={() => setTransitionFn(() => t.fn)}
            style={{
              padding: '6px 14px', borderRadius: 8, border: 'none', cursor: 'pointer',
              fontSize: 13, fontWeight: 500,
              background: transitionFn === t.fn ? '#6366f1' : '#e2e8f0',
              color: transitionFn === t.fn ? '#fff' : '#334155',
            }}
          >
            {t.label}
          </button>
        ))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
        {images.map((img, i) => (
          <button
            key={i}
            onClick={() => setIndex(i)}
            style={{
              position: 'relative', aspectRatio: '4 / 3', borderRadius: 8,
              overflow: 'hidden', border: 'none', padding: 0, cursor: 'pointer',
              background: '#e2e8f0',
            }}
          >
            <img
              src={img.src}
              alt={img.title}
              style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
            />
          </button>
        ))}
      </div>
      <LightboxOverlay
        isOpen={index !== null}
        images={images}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
        transitionFn={transitionFn}
      />
    </div>
  );
}

Para rodar uma galeria como apresentação e salvar o original de cada foto, veja a receita Galeria com apresentação.

Slides de vídeo (opcionais)

O suporte a vídeo é inteiramente opcional e sai no tree-shaking — quem usa só imagens não paga nada a mais no bundle. Importe useVideoSlideRenderer e ligue o que ele devolve ao LightboxOverlay. O hook cuida sozinho dos estados de carregamento, do som e do ciclo de vida do vídeo.

tsx
import {
  LightboxOverlay,
  useVideoSlideRenderer,
  type LightboxItem,
} from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

const items: LightboxItem[] = [
  { src: '/photo.jpg', title: 'Photo' },
  {
    src: '/clip.mp4',
    type: 'video',
    poster: '/clip-thumb.jpg',
    title: 'Video Clip',
  },
];

function Gallery() {
  const [index, setIndex] = useState<number | null>(null);
  const isOpen = index !== null;
  const { renderSlide, renderControls, SoundProvider } =
    useVideoSlideRenderer(items);

  return (
    <SoundProvider>
      {/* thumbnails… */}
      <LightboxOverlay
        isOpen={isOpen}
        images={items}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
        renderSlide={renderSlide}
        renderControls={renderControls}
      />
    </SoundProvider>
  );
}
Como funciona
  • O hook devolve SoundProvider — envolva seu overlay nele para que o mudo funcione
  • Os vídeos tocam sozinhos (sem som, por padrão) quando o slide fica ativo
  • Um elemento de vídeo compartilhado é reaproveitado entre os slides, o que mantém o som contínuo no iOS
  • O botão de som aparece sozinho nos slides de vídeo, com alternância reativa
  • Itens sem type: 'video' são exibidos como imagens (compatível com o que já existia)

Personalização

Controles próprios

Use renderControls para substituir o botão de fechar, o contador e o botão de tela cheia padrão. Componha com os subcomponentes exportados:

tsx
import {
  LightboxOverlay,
  CloseButton,
  Counter,
  FullscreenButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderControls={({ onClose, activeIndex, count, isFullscreen, onToggleFullscreen }) => (
    <div style={{ position: 'absolute', top: 12, left: 16, right: 16, display: 'flex', justifyContent: 'space-between', zIndex: 10 }}>
      <Counter currentIndex={activeIndex} count={count} />
      <div>
        <FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />
        <CloseButton onClick={onClose} />
      </div>
    </div>
  )}
/>

Camada de informações própria

Use renderInfo para substituir o gradiente padrão de título e descrição, ou passe renderInfo={() => null} para escondê-lo por completo:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderInfo={({ item, index }) => (
    <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, background: 'linear-gradient(transparent, rgba(0,0,0,0.8))', color: '#fff', zIndex: 10 }}>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </div>
  )}
/>

Navegação própria

Use renderNavigation para substituir as setas padrão de avançar e voltar:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderNavigation={({ onPrev, onNext, activeIndex, count }) => (
    <div style={{ position: 'absolute', bottom: 24, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 12, zIndex: 10 }}>
      <button onClick={onPrev} disabled={activeIndex === 0}>Prev</button>
      <span>{activeIndex + 1} / {count}</span>
      <button onClick={onNext} disabled={activeIndex === count - 1}>Next</button>
    </div>
  )}
/>

Slide próprio

Use renderSlide para um conteúdo de slide totalmente seu. Devolva null para cair no slide de imagem padrão:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderSlide={({ item, index, size, isActive, onReady, onError }) => {
    // Custom CTA on last slide
    if (index === images.length - 1) {
      return (
        <div style={{ width: size[0], height: size[1], display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
          <h2>View all photos</h2>
        </div>
      );
    }
    return null; // default image slide
  }}
/>

Carregamento de conteúdo e tratamento de erros

A galeria acompanha o carregamento e os erros de cada slide. Um girador aparece enquanto o conteúdo carrega; um ícone de imagem quebrada aparece quando a mídia falha. As URLs com erro ficam em cache, então voltar a elas mostra o erro na hora, sem nova tentativa.

Callbacks de ciclo de vida

Ao usar renderSlide, chame estes callbacks para comandar o indicador de carregamento:

CallbackQuando chamar
onReadyA imagem carregou ou o vídeo começou a tocar. Limpa os estados de carregamento e erro.
onWaitingO vídeo está carregando no meio da reprodução. Mostra o indicador de carregamento.
onErrorO conteúdo não carregou. Mostra a camada de erro e registra a URL como quebrada.
tsx
// Inside renderSlide — wire callbacks to your custom media
renderSlide={({ item, index, size, isActive, onReady, onWaiting, onError }) => (
  <div style={{ width: size[0], height: size[1] }}>
    {item.type === 'video' ? (
      <video
        src={item.src}
        poster={item.poster}
        autoPlay={isActive}
        onCanPlay={onReady}
        onWaiting={onWaiting}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
      />
    ) : (
      <img
        src={item.src}
        onLoad={onReady}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
      />
    )}
  </div>
)}

Interface própria de carregamento e erro

Troque o girador e o ícone de erro padrão por componentes seus:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={() => setIsOpen(false)}
  renderLoading={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: '#fff', fontSize: 14,
    }}>
      Loading image {activeIndex + 1}...
    </div>
  )}
  renderError={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      gap: 12, color: 'rgba(255,255,255,0.5)',
    }}>
      <span style={{ fontSize: 48 }}>!</span>
      <span>Failed to load content</span>
    </div>
  )}
/>

Estado na URL

Ver demonstração ao vivo →

LightboxUrlOverlay é um componente à parte cujo estado aberto vive na barra de endereços. Monte um controlador com useOverlayUrlState, de @reelkit/react, e entregue-o como controller: a galeria se abre quando o parâmetro nomeia um slide e se fecha quando ele some. Os links dão para compartilhar, e o botão voltar fecha a galeria em vez de sair da página.

Chaves prontas

Você pode endereçar os slides com uma chave pronta — espalhe urlIndexKey (por posição) ou urlStableIdKey (por um id estável) no controlador — as duas reexportadas por @reelkit/react. Veja o guia de estado na URL e a API do core.

tsx
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/react';
import { LightboxUrlOverlay } from '@reelkit/react-lightbox';
import { Link } from 'react-router-dom';

const photo = useOverlayUrlState({
  param: 'photo',
  ...urlIndexKey(() => images.length),
});

// Opening is a link — the href is the open action. No open flag, no handler:
// the overlay reads the URL and opens itself.
{images.map((image, i) => (
  <Link key={image.src} to={`?photo=${i}`}>
    <img src={image.src} />
  </Link>
))}

<LightboxUrlOverlay controller={photo} images={images} />

O hook recebe um objeto de opções e devolve um UrlStateController (com set, index, value). Guarde-o para o controle programático: set é a escrita de baixo nível que o overlay usa internamente (troca de slide, e set(null) para fechar). Ele também conduz o overlay por código — set(index) o abre, exatamente como navegar até o parâmetro. Ainda assim, prefira um link para abrir: o href dá para compartilhar, abre em nova aba e o botão voltar o fecha — tudo de graça, sem nenhum handler.

As opções completas de useOverlayUrlState (param, adapter, codec, locator) estão na referência da API para React.

O próprio LightboxUrlOverlay recebe apenas controller (obrigatório), um onClose opcional e todas as props visuais e de comportamento que o LightboxOverlay aceita (images, ariaLabel, transitionFn, as render props e assim por diante) — mas nenhum isOpen.

  • Abrir custa uma entrada no histórico. Percorrer os slides a substitui, então cem deslizes não acrescentam nenhuma — um único passo para trás sempre sai da galeria. Voltar fecha; não passa de foto em foto.
  • Um link compartilhado como ?photo=3 abre a galeria naquele slide. Fechar um link que chegou junto com a página remove o parâmetro ali mesmo, em vez de tirar o leitor do seu site.
  • Voltar só fecha quando você abriu de dentro do app — o link empilhou uma entrada, então voltar desempilha até a galeria. Um link compartilhado aberto direto em uma aba nova não tem histórico atrás de si, então o voltar do navegador sai do site; o botão de fechar ou o Escape removem o parâmetro ali mesmo e mantêm você na galeria. Os adaptadores de roteador que acompanham o pacote atestam um empilhamento na mesma página; um adaptador próprio que não sabe dizer como a entrada chegou recebe o fechamento no lugar, o que deixa uma cópia da página no histórico — um passo para trás parece então não fazer nada, mas nada reabre.
  • Um parâmetro que não nomeia slide nenhum — um favorito velho, um valor editado à mão — é retirado da URL, em vez de deixar a barra de endereços afirmando um slide que não abre.

Em um app com rotas, passe um adaptador. Escrever direto no histórico deixa a localização do roteador desatualizada, e a navegação seguinte derruba o parâmetro.

tsx
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';

const adapter = useReactRouterUrlAdapter();
const photo = useOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => images.length),
});

<LightboxUrlOverlay controller={photo} images={images} />

Abrir é um link. Como o estado aberto vive na URL, a miniatura é um link comum — sem handler de clique — e o comportamento nativo do navegador vem junto: abrir em nova aba, copiar o endereço, pré-visualizar ao passar o mouse. Em um app com rotas, use o link do roteador para que a navegação siga no cliente.

tsx
import { Link } from 'react-router-dom';

// The href is the open action — no onClick, no open flag.
{images.map((image, i) => (
  <Link key={image.src} to={`?photo=${i}`}>
    <img src={image.src} />
  </Link>
))}

<LightboxUrlOverlay controller={photo} images={images} />

Prefira uma identidade estável nos links compartilhados. O índice é posicional, então um ?photo=3 favoritado abre outra imagem assim que a lista muda de ordem. urlStableIdKey usa o id estável de cada item, varrendo a lista atual — uma chamada resolve o caso comum.

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  ...urlStableIdKey({ items: () => images }),
});

<LightboxUrlOverlay controller={photo} images={images} />

Passe hashCodec: base64UrlCodec para escrever o id em base64url na URL — é um disfarce reversível, não um hash criptográfico.

Quer endereçar por outro campo (um slug), ou paginar um feed infinito com locateAsync? Monte o codec e o locator você mesmo:

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.slug === id),
    identify: (index) => images[index].slug,
  },
});

<LightboxUrlOverlay controller={photo} images={images} />

Galerias infinitas ou paginadas. O locate síncrono só responde pelas imagens já carregadas — um link compartilhado para a imagem 400 de um feed que carregou 20 volta vazio. locateAsync é o plano B, chamado somente quando locate falha.

Atalho

Vai endereçar pelo id do item? Dispense o codec e o locator escritos à mão — passe locateAsync direto para urlStableIdKey({ items, locateAsync }) (ele busca quando falha e então devolve o índice). A versão mais completa abaixo serve para endereçar por outro campo, ou para ter controle total.

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.id === id),
    identify: (index) => images[index].id,
    locateAsync: async (id) => {
      const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
      if (!loaded) return null; // exhausted — link names no image
      setImages(loaded); // commit — the overlay renders from this state
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});

<LightboxUrlOverlay controller={photo} images={images} />
  • A forma de carregar é escolha sua — busque páginas contíguas até o alvo, ou busque só aquela imagem e a acrescente. A URL endereça por identidade, não por posição, então findIndex devolve onde quer que o item tenha caído.
  • Enquanto locateAsync está pendente, a galeria fica fechada e o parâmetro é deixado em paz, então o link direto sobrevive à busca. Um null ou uma rejeição derrubam o parâmetro.
  • Uma resposta que chega depois de a URL ter mudado, depois de um fechamento ou depois da desmontagem é descartada — uma busca lenta não pode abrir um slide que ninguém pediu.
  • Nada é renderizado enquanto a busca corre; esse estado de carregamento já pertence à página, então desenhe o seu próprio esqueleto.
  • Não existe tempo limite — a galeria não tem como saber o próprio tamanho. Termine com null quando a paginação se esgotar, ou o overlay fica fechado para sempre.
  • O que locateAsync devolver é palavra final — o índice dos dados que ele acabou de buscar, tomado como está, sem reler images.

Avanço automático

Defina autoAdvance para tocar a galeria como uma apresentação assim que ela abre. Vem desligado, então uma galeria que já existe renderiza e se comporta como antes.

tsx
import { LightboxOverlay } from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  autoAdvance
  autoAdvanceInterval={4000}
/>;
  • Imagens ficam por autoAdvanceInterval (5000 ms por padrão), contados a partir do momento em que a imagem carregou, então uma imagem lenta nunca é pulada antes de alguém vê-la. Uma imagem que falha ao carregar conta a partir do erro.
  • Vídeos tocam uma vez em vez de repetir e passam adiante quando terminam. Com useVideoSlideRenderer isso já vem ligado; um renderSlide próprio lê autoAdvance das suas props e avisa o fim por onEnded.
  • Ela espera enquanto o mouse repousa sobre um controle (um botão, um link ou a legenda), durante um deslize e enquanto a página está oculta, e depois continua de onde parou.
  • Navegar à mão (setas, teclas, roda do mouse, deslize) começa o intervalo do novo slide do zero; a apresentação continua ligada.
  • No último slide ela para, a menos que loop esteja definido. O botão de play/pause nos controles mostra o estado e deixa quem assiste alterná-lo.

Um renderControls próprio recebe isAutoAdvancing e, só quando autoAdvance está ligado, onToggleAutoAdvance — renderize um PlayPauseButton quando ele estiver lá. A temporização em si é o createSlideshowController de @reelkit/core; veja o guia de reprodução automática para levar o mesmo comportamento a um player seu, e a API do core para os seus métodos.

Download

Defina showDownload para acrescentar um botão de download aos controles. Ele salva o item ativo a partir do seu downloadUrl, ou de src na falta dele, com o nome downloadName, ou com o último segmento da URL na falta dele.

tsx
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';

const images: LightboxItem[] = [
  {
    src: 'https://cdn.example.com/beach-1600.jpg',
    // Save the original rather than the resized preview
    downloadUrl: 'https://cdn.example.com/beach-original.jpg',
    downloadName: 'beach.jpg',
  },
];

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  showDownload
/>;

O arquivo é buscado e salvo, porque o navegador ignora o atributo download num link de outra origem e abriria o arquivo em vez de salvá-lo. Quando a busca falha — em geral um servidor de outra origem que não envia cabeçalhos CORS — o arquivo abre numa nova aba, onde quem assiste ainda pode salvá-lo. Sirva os arquivos com Access-Control-Allow-Origin para salvar com um clique. O botão fica desativado enquanto um download está em andamento.

Um renderControls próprio recebe isDownloading e, só quando showDownload está ligado, onDownload — renderize um DownloadButton quando ele estiver lá. O helper é o downloadFile de @reelkit/core; veja o guia de download.

Referência da API

Props do LightboxOverlay

LightboxOverlayProps

PropTipoPadrãoDescrição
isOpenbooleanobrigatórioControla a visibilidade da galeria. Para um estado aberto guiado pela URL, use o componente à parte LightboxUrlOverlay — veja Estado na URL, abaixo.
imagesLightboxItem[]obrigatórioArray de imagens a exibir
ariaLabelstring'Image gallery'Rótulo acessível da região do diálogo; anunciado pelos leitores de tela quando a galeria abre
initialIndexnumber0Índice da imagem inicial
transitionFnTransitionTransformFnslideTransitionFunção da transição entre slides. Importe uma das prontas (slideTransition, flipTransition, lightboxFadeTransition, lightboxZoomTransition) ou passe a sua. Sem esta prop, vale slideTransition.
autoAdvancebooleanfalseToca a galeria como uma apresentação. Veja Avanço automático.
autoAdvanceIntervalnumber5000Quanto tempo cada imagem fica durante a apresentação, em milissegundos, contado a partir do momento em que carregou
showDownloadbooleanfalseAcrescenta um botão de download para o item ativo. Veja Download.
apiRefMutableRefObject<ReelApi>-Ref para alcançar a API do Reel
renderControls(props: ControlsRenderProps) => ReactNode-Controles próprios, substituem o botão de fechar, o contador e o botão de tela cheia padrão
renderNavigation(props: NavigationRenderProps) => ReactNode-Navegação própria, substitui as setas padrão de avançar e voltar
renderInfo(props: InfoRenderProps) => ReactNode-Camada de informações própria, substitui o gradiente padrão de título e descrição. Devolva null para esconder.
renderSlide(props: SlideRenderProps) => ReactNode | null-Renderização própria do slide. Recebe { item, index, size, isActive, autoAdvance, onReady, onWaiting, onError, onEnded }. Devolva null para cair no padrão.
renderLoading(props: { item: LightboxItem; activeIndex: number }) => ReactNode-Indicador de carregamento próprio, substitui o girador padrão
renderError(props: { item: LightboxItem; activeIndex: number }) => ReactNode-Indicador de erro próprio, substitui o ícone padrão de erro

Props do LightboxUrlOverlay

LightboxUrlOverlayProps

Aceita todas as props visuais e de comportamento acima, menos isOpen, substituída por controller. initialIndex é ignorada aqui — quem escolhe o slide é a posição do controlador, então um valor passado ao lado dela seria sobrescrito a cada abertura.

PropTipoPadrãoDescrição
controllerUrlStateControllerobrigatórioControlador vindo de useOverlayUrlState. A posição dele decide se o overlay está aberto e qual slide aparece; o overlay escreve de volta por ele na troca de slide e no fechamento.

Callbacks

PropTipoDescrição
onClose() => voidChamado quando a galeria fecha. Obrigatório no LightboxOverlay (o estado aberto é seu, então o fechamento também); opcional no LightboxUrlOverlay, onde quem fecha é a URL — passe apenas se quiser reagir depois do fechamento.
onSlideChange(index: number) => voidChamado depois da troca de slide

Props do Reel (repassadas)

Estas props são repassadas ao componente Reel por baixo. O tipo delas é ReelProxyProps, o subconjunto das props do Reel que o overlay repassa; use-o para tipar um wrapper que as encaminha.

PropTipoPadrãoDescrição
loopbooleanfalseLiga o ciclo infinito
enableNavKeysbooleantrueLiga a navegação por teclado
enableWheelbooleantrueLiga a navegação pela roda do mouse
wheelDebounceMsnumber200Debounce da roda do mouse (ms)
transitionDurationnumber300Duração da animação de transição (ms)
swipeDistanceFactornumber0.12Limiar do deslize (0-1)
swipeToCloseDirection'up' | 'down''up'Direção do gesto de deslizar para fechar no celular

Tipos

LightboxItem

typescript
interface LightboxItem {
  src: string;
  type?: 'image' | 'video';  // defaults to 'image'
  poster?: string;            // thumbnail for video items
  title?: string;
  description?: string;
  width?: number;
  height?: number;
  downloadUrl?: string;       // what the download button saves; defaults to src
  downloadName?: string;      // file name; defaults to the URL's last segment
}

ControlsRenderProps

typescript
interface ControlsRenderProps {
  item: LightboxItem;
  activeIndex: number;
  count: number;
  isFullscreen: boolean;
  isAutoAdvancing: boolean;
  isDownloading: boolean;
  onClose: () => void;
  onToggleFullscreen: () => void;
  onToggleAutoAdvance?: () => void; // only with autoAdvance
  onDownload?: () => void;          // only with showDownload
}
typescript
interface NavigationRenderProps {
  item: LightboxItem;
  activeIndex: number;
  count: number;
  onPrev: () => void;
  onNext: () => void;
}

SlideRenderProps

typescript
interface SlideRenderProps {
  item: LightboxItem;
  index: number;
  size: [number, number];
  isActive: boolean;
  autoAdvance: boolean; // a video should play once, not loop
  onReady: () => void;
  onWaiting: () => void;
  onError: () => void;
  onEnded: () => void;  // a video played to its end
}

InfoRenderProps

typescript
interface InfoRenderProps {
  item: LightboxItem;
  index: number;
}

Subcomponentes

Subcomponentes reutilizáveis para compor controles próprios com renderControls.

CloseButton

O botão X de fechar padrão. Props (CloseButtonProps): onClick, além de className e style opcionais.

tsx
import { CloseButton } from '@reelkit/react-lightbox';

<CloseButton onClick={onClose} />

Counter

Pílula contadora de imagens, mostrando "1 / 3". Props (CounterProps): currentIndex (começando em zero), count, além de className e style opcionais.

tsx
import { Counter } from '@reelkit/react-lightbox';

<Counter currentIndex={activeIndex} count={count} />

FullscreenButton

Botão de tela cheia (ícone de maximizar/minimizar). Props (FullscreenButtonProps): isFullscreen, onToggle, além de className e style opcionais.

tsx
import { FullscreenButton } from '@reelkit/react-lightbox';

<FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />

SoundButton

Botão de mudo para os slides de vídeo (ícone Volume2/VolumeX). Vem incluído automaticamente no renderControls de useVideoSlideRenderer. Para usá-lo isolado dentro de controles próprios, alcance o estado do som por useSoundState. Props (SoundButtonProps): isMuted, onToggle, além de className e style opcionais.

tsx
import { SoundButton } from '@reelkit/react-lightbox';
import { useSoundState } from '@reelkit/react';

// Inside a component wrapped in SoundProvider:
function CustomControls({ onClose }) {
  const soundState = useSoundState();

  return (
    <div>
      <SoundButton
        isMuted={soundState.muted.value}
        onToggle={soundState.toggle}
      />
      <button onClick={onClose}>Close</button>
    </div>
  );
}

PlayPauseButton

Alternador da apresentação (ícone Play/Pause, aria-pressed enquanto toca). Props (PlayPauseButtonProps): isPlaying, onToggle, além de className e style opcionais. Os controles padrão o mostram quando autoAdvance está ligado.

tsx
import {
  LightboxOverlay,
  PlayPauseButton,
  CloseButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  autoAdvance
  renderControls={({ isAutoAdvancing, onToggleAutoAdvance, onClose }) => (
    <>
      {onToggleAutoAdvance && (
        <PlayPauseButton
          isPlaying={isAutoAdvancing}
          onToggle={onToggleAutoAdvance}
        />
      )}
      <CloseButton onClick={onClose} />
    </>
  )}
  {...props}
/>;

DownloadButton

Botão de download (ícone Download), desativado enquanto isDownloading. Props (DownloadButtonProps): isDownloading, onClick, além de className e style opcionais. Os controles padrão o mostram quando showDownload está ligado.

tsx
import {
  LightboxOverlay,
  DownloadButton,
  CloseButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  showDownload
  renderControls={({ isDownloading, onDownload, onClose }) => (
    <>
      {onDownload && (
        <DownloadButton isDownloading={isDownloading} onClick={onDownload} />
      )}
      <CloseButton onClick={onClose} />
    </>
  )}
  {...props}
/>;

Hooks

useVideoSlideRenderer

Hook do suporte opcional a vídeo. Devolve renderSlide, renderControls e SoundProvider — envolva o overlay no SoundProvider e passe as funções de renderização. O tipo devolvido é UseVideoSlideRendererResult, que também traz hasVideo, true quando algum item tem type: 'video'.

O renderSlide dele desenha os itens de vídeo com LightboxVideoSlide, um slide de vídeo que toca por um único elemento <video> compartilhado, para o som continuar funcionando no iOS, e lembra a posição de cada vídeo. Renderize-o você mesmo só a partir de um renderSlide próprio. As props dele (LightboxVideoSlideProps) são src, poster, isActive, size, slideKey (chave da posição salva e do quadro capturado), loop (padrão true) e os callbacks onPlaying, onWaiting, onError, onEnded.

typescript
import { useVideoSlideRenderer } from '@reelkit/react-lightbox';

const { renderSlide, renderControls, SoundProvider, hasVideo } =
  useVideoSlideRenderer(items);

// SoundProvider  — wrap LightboxOverlay in this for mute/unmute support
// renderSlide    — pass to LightboxOverlay's renderSlide prop
// renderControls — pass to LightboxOverlay's renderControls prop
//                  (includes Counter, FullscreenButton, SoundButton, CloseButton)
// hasVideo       — true if items contain at least one video

useFullscreen

Hook para controlar o estado de tela cheia com suporte entre navegadores.

tsx
import { useRef } from 'react';
import { useFullscreen } from '@reelkit/react';

function CustomLightbox() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isFullscreen, requestFullscreen, exitFullscreen, toggleFullscreen] =
    useFullscreen({ ref: containerRef });

  return (
    <div ref={containerRef}>
      <button onClick={toggleFullscreen}>
        {isFullscreen.value ? 'Exit Fullscreen' : 'Enter Fullscreen'}
      </button>
    </div>
  );
}

Transições

Passe qualquer TransitionTransformFn pela prop transitionFn. Importar apenas a transição que você usa deixa o bundler descartar as outras. Sem esta prop, vale slideTransition.

FunçãoDe onde vemDescrição
slideTransition@reelkit/react-lightboxDeslizamento horizontal comum (padrão)
lightboxFadeTransition@reelkit/react-lightboxEsmaecimento cruzado entre as imagens
flipTransition@reelkit/react-lightboxEfeito de virada de carta em 3D
lightboxZoomTransition@reelkit/react-lightboxAproximação, do menor até o tamanho normal
tsx
import {
  LightboxOverlay,
  lightboxFadeTransition,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  initialIndex={0}
  onClose={handleClose}
  transitionFn={lightboxFadeTransition}
/>

Função de transição própria

Escreva a sua TransitionTransformFn e passe-a por transitionFn. A assinatura é a mesma das transições do slider do core.

tsx
import { LightboxOverlay } from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';

const customFade: TransitionTransformFn = (offset, size) => ({
  transform: `translate3d(${offset * size[0]}px, 0, 0)`,
  opacity: 1 - Math.min(Math.abs(offset), 1),
});

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  transitionFn={customFade}
  onClose={() => setIsOpen(false)}
/>

Classes CSS

Todos os elementos da interface usam classes CSS simples (não são CSS modules), que dá para alcançar com seletores de especificidade maior em uma folha de estilo carregada depois de @reelkit/react-lightbox/styles.css. Para mudar cor, tamanho e z-index, prefira as propriedades CSS documentadas na seção Temas, abaixo.

ClasseComponenteDescrição
.rk-lightbox-overlayOverlayContêiner raiz (fundo em tela cheia)
.rk-lightbox-spinnerOverlayGirador de carregamento padrão
.rk-lightbox-img-errorOverlayContêiner do estado de erro (imagem ou vídeo quebrado)
.rk-lightbox-img-error-textOverlayTexto do estado de erro
.rk-lightbox-swipe-hintOverlayDica de deslize no celular
.rk-lightbox-controls-leftControlsContêiner dos controles no canto superior esquerdo
.rk-lightbox-btnControlsBotões de controle (tela cheia e afins)
.rk-lightbox-play-pauseControlsBotão de play/pause da apresentação (com .rk-lightbox-btn)
.rk-lightbox-downloadControlsBotão de download (com .rk-lightbox-btn)
.rk-lightbox-closeControlsBotão de fechar
.rk-lightbox-counterControlsEtiqueta contadora de imagens
.rk-lightbox-navNavigationSetas de navegação (as duas)
.rk-lightbox-nav-prevNavigationSeta de voltar
.rk-lightbox-nav-nextNavigationSeta de avançar
.rk-lightbox-infoInfoContêiner de título e descrição
.rk-lightbox-titleInfoTítulo da imagem
.rk-lightbox-descriptionInfoDescrição da imagem
.rk-lightbox-slideSlideContêiner do slide
.rk-lightbox-imgSlideElemento de imagem
.rk-lightbox-video-containerVideoSlideContêiner do slide de vídeo (opcional)
.rk-lightbox-video-elementVideoSlideElemento de vídeo (opcional)
.rk-lightbox-video-posterVideoSlideImagem de pôster do vídeo (opcional)

Temas

Cada cor, tamanho, z-index e transição vive em uma propriedade CSS. Sobrescreva uma ou várias em :root (ou em qualquer ancestral da galeria) para trocar o tema sem mexer no código dos componentes.

TokenPadrãoO que controla
--rk-lightbox-overlay-bg#000Cor do fundo em tela cheia
--rk-lightbox-overlay-z9999z-index do overlay
--rk-lightbox-top-shade-height80pxAltura do gradiente do topo
--rk-lightbox-top-shade-bglinear-gradient(rgba(0,0,0,0.6), transparent)Cor do gradiente do topo
--rk-lightbox-edge-padding16pxRecuo da borda para fechar, navegação e controles do canto superior esquerdo
--rk-lightbox-controls-gap12pxEspaço entre os controles do canto superior esquerdo
--rk-lightbox-transition0.2sDuração da transição dos botões ao passar o mouse
--rk-lightbox-blur8pxRaio do desfoque atrás de botões e etiquetas
--rk-lightbox-btn-bgrgba(0, 0, 0, 0.5)Fundo padrão dos botões de fechar, navegação e dos pequenos
--rk-lightbox-btn-bg-hoverrgba(255, 255, 255, 0.2)Fundo desses botões ao passar o mouse
--rk-lightbox-btn-fg#fffCor do ícone desses botões
--rk-lightbox-btn-size36pxTamanho dos botões pequenos (tela cheia e afins)
--rk-lightbox-btn-disabled-opacity0.5Opacidade de um botão pequeno desativado (download em andamento)
--rk-lightbox-close-size40pxTamanho do botão de fechar
--rk-lightbox-nav-size48pxTamanho das setas de avançar e voltar
--rk-lightbox-nav-opacity0.7Opacidade das setas em repouso
--rk-lightbox-counter-fg#fffCor do texto do contador
--rk-lightbox-counter-bgrgba(0, 0, 0, 0.5)Fundo da etiqueta do contador
--rk-lightbox-counter-size14pxTamanho da fonte do contador
--rk-lightbox-counter-padding6px 12pxRespiro interno da etiqueta do contador
--rk-lightbox-counter-radius20pxArredondamento da etiqueta do contador
--rk-lightbox-spinner-size28pxLargura e altura do girador padrão
--rk-lightbox-spinner-trackrgba(255, 255, 255, 0.2)Cor da trilha do girador
--rk-lightbox-spinner-fg#fffCor do indicador do girador
--rk-lightbox-spinner-duration0.8sDuração de uma volta do girador
--rk-lightbox-error-fgrgba(255, 255, 255, 0.4)Cor do ícone e do texto de erro
--rk-lightbox-error-text-size13pxTamanho da fonte da mensagem de erro
--rk-lightbox-info-bglinear-gradient(transparent, rgba(0,0,0,0.8))Gradiente que escurece o fundo da legenda
--rk-lightbox-info-padding24pxRespiro interno da legenda
--rk-lightbox-title-size18pxTamanho da fonte do título
--rk-lightbox-description-size14pxTamanho da fonte da descrição
--rk-lightbox-info-fg#fffCor do texto da legenda
--rk-lightbox-hint-fgrgba(255, 255, 255, 0.5)Cor do texto da dica de deslize
--rk-lightbox-hint-bgrgba(0, 0, 0, 0.3)Fundo da etiqueta da dica de deslize
--rk-lightbox-hint-duration3sDuração total da dica de deslize, entrando e saindo
--rk-lightbox-video-bg#000Cor das faixas em volta do <video>

Cole o trecho abaixo em uma folha de estilo carregada depois de @reelkit/react-lightbox/styles.css.

css
/* Brand the lightbox */
:root {
  --rk-lightbox-overlay-bg: #0f172a;
  --rk-lightbox-btn-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-btn-bg-hover: rgba(168, 85, 247, 0.85);
  --rk-lightbox-nav-size: 56px;
  --rk-lightbox-counter-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-info-bg: linear-gradient(
    transparent,
    rgba(99, 102, 241, 0.55) 60%,
    rgba(168, 85, 247, 0.85)
  );
}

Acessibilidade

A raiz do overlay é um diálogo modal (role="dialog", aria-modal="true"). Defina ariaLabel para mudar o que o leitor de tela anuncia; o padrão é "Image gallery". Cada slide carrega role="group", aria-roledescription="slide" e aria-label="Image N of M".

A galeria captura o foco ao abrir e o devolve ao gatilho ao fechar. Tab e Shift+Tab percorrem os elementos focáveis lá dentro; um foco que escapa (clique fora, foco por código) é trazido de volta. Implementado com captureFocusForReturn e createFocusTrap, de @reelkit/core.

Atalhos de teclado

TeclaAção
ArrowLeftImagem anterior
ArrowRightPróxima imagem
EscapeFecha a galeria (ou sai da tela cheia, se estiver nela)