Lightbox

Eine Komponente für Bild- und Videogalerien im Vollbild, die Lightbox aus @reelkit/react-lightbox.

Live-Demo ansehen →

Funktionen

Bilder und Video
Video-Slides eingebaut
Touch-Gesten
Wischen zum Navigieren
Wischen zum Schließen
Nach oben wischen zum Schließen
Tastaturnavigation
Pfeiltasten + Escape
Vollbild
Browserübergreifende API
Transitions
Slide, Fade, Flip, Zoom-in
Vorladen
Benachbarte Bilder werden vorab geladen
Ton an/aus
Stummschalten pro Slide
Ladezustände
Spinner + eigenes Rendering
Fehlerbehandlung
Fehlersymbol + eigenes Rendering
Render Props
6 anpassbare Render-Bereiche
Hooks
useVideoSlideRenderer + useFullscreen
URL-State
Teilbare Links mit Lesezeichen
Diashow
Optionales Weiterblättern mit Play/Pause
Download
Optionaler Button, der das aktive Element speichert

Installation

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

Vergiss nicht, die Styles zu importieren:

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

Die Standard-Controls nutzen lucide-react für die Icons. Wenn du eine andere Icon-Bibliothek bevorzugst, gib mit renderControls und renderNavigation deine eigenen vor.

Schnellstart

Die Komponente LightboxOverlay zeigt Bilder im Vollbild an. Übergib ein Array von LightboxItem-Objekten und steuere die Sichtbarkeit über einen Index, der auch null sein kann.

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>
  );
}

Wie du eine Galerie als Diashow abspielst und das Original jedes Fotos speicherst, zeigt das Rezept „Galerie als Diashow“.

Video-Slides (optional)

Die Video-Unterstützung ist komplett optional und per Tree-Shaking entfernbar: Wer nur Bilder zeigt, zahlt keinen Byte mehr im Bundle. Importiere useVideoSlideRenderer und gib seine Rückgabewerte an LightboxOverlay weiter. Der Hook kümmert sich automatisch um Ladezustände, den Ton und den Lebenszyklus der Videos.

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>
  );
}
So funktioniert es
  • Der Hook gibt SoundProvider zurück: Umschließe dein Overlay damit, damit das Stummschalten funktioniert
  • Videos starten automatisch (standardmäßig stumm), sobald die Slide aktiv wird
  • Ein gemeinsames Video-Element wird über alle Slides wiederverwendet, damit der Ton auf iOS nicht abreißt
  • Der Ton-Button erscheint auf Video-Slides von selbst, mit einem reaktiven Schalter für den Ton
  • Elemente ohne type: 'video' werden als Bilder gerendert (abwärtskompatibel)

Anpassung

Eigene Controls

Mit renderControls ersetzt du den Standard-Schließen-Button, den Zähler und den Vollbild-Schalter. Kombiniere dafür die exportierten Unterkomponenten:

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>
  )}
/>

Eigenes Info-Overlay

Mit renderInfo ersetzt du den Standard-Verlauf mit Titel und Beschreibung, oder du übergibst renderInfo={() => null}, um ihn ganz auszublenden:

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>
  )}
/>

Eigene Navigation

Mit renderNavigation ersetzt du die Standard-Pfeile für zurück und weiter:

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>
  )}
/>

Eigene Slide

Mit renderSlide gestaltest du den Inhalt einer Slide komplett selbst. Gib null zurück, um auf die Standard-Bild-Slide zurückzufallen:

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
  }}
/>

Laden von Inhalten und Fehlerbehandlung

Die Lightbox verfolgt den Lade- und Fehlerzustand jeder Slide. Ein Spinner erscheint, solange der Inhalt lädt; ein Symbol für ein kaputtes Bild erscheint bei Medien, die nicht geladen werden konnten. Fehlerhafte URLs werden gemerkt, sodass ein erneuter Besuch den Fehler sofort zeigt, ohne es noch einmal zu versuchen.

Lebenszyklus-Callbacks

Wenn du renderSlide verwendest, rufst du diese Callbacks auf, um die Ladeanzeige zu steuern:

CallbackWann aufrufen
onReadyDas Bild ist geladen oder das Video spielt. Setzt Lade- und Fehlerzustand zurück.
onWaitingDas Video puffert während der Wiedergabe. Zeigt die Ladeanzeige.
onErrorDer Inhalt konnte nicht geladen werden. Zeigt das Fehler-Overlay und merkt sich die URL als defekt.
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>
)}

Eigene Lade- und Fehleranzeige

Ersetze den Standard-Spinner und das Fehlersymbol durch eigene Komponenten:

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>
  )}
/>

URL-State

Live-Demo ansehen →

LightboxUrlOverlay ist eine eigene Komponente, deren Öffnungszustand in der Adressleiste steht. Erstelle mit useOverlayUrlState aus @reelkit/react einen Controller und übergib ihn als controller: Die Galerie öffnet sich selbst, wenn der Parameter eine Slide benennt, und schließt sich, wenn er verschwindet. Links lassen sich teilen, und der Zurück-Button schließt die Galerie, statt die Seite zu verlassen.

Eingebaute Schlüssel

Du kannst Slides mit einem eingebauten Schlüssel ansprechen: Übergib urlIndexKey (nach Position) oder urlStableIdKey (nach einer stabilen id) per Spread an den Controller. Beide werden von @reelkit/react re-exportiert. Mehr dazu im Guide zum URL-State und in der Core API.

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} />

Der Hook nimmt ein Options-Objekt und gibt einen UrlStateController zurück (mit set, index, value). Behalte ihn für die Steuerung per Code: set ist der Schreibzugriff auf unterster Ebene, den das Overlay intern nutzt (Slide-Wechsel, und set(null) zum Schließen). Damit steuerst du das Overlay auch per Code: set(index) öffnet es, genau wie das Navigieren zum Parameter. Zum Öffnen ist ein Link trotzdem die bessere Wahl: Der href lässt sich teilen, öffnet sich in einem neuen Tab, und der Zurück-Button schließt es wieder, alles ohne Handler.

Alle Optionen von useOverlayUrlState (param, adapter, codec, locator) findest du in der React-API-Referenz.

LightboxUrlOverlay selbst nimmt nur controller (erforderlich), ein optionales onClose und dazu jedes Darstellungs- und Verhaltens-Prop, das auch LightboxOverlay nimmt (images, ariaLabel, transitionFn, die Render Props und so weiter), aber kein isOpen.

  • Das Öffnen kostet einen Eintrag im Verlauf. Das Blättern durch Slides ersetzt ihn, sodass hundert Wischbewegungen keinen einzigen hinzufügen: Ein Schritt zurück verlässt die Galerie immer. Zurück schließt; es blättert nicht durch die Fotos.
  • Ein geteilter Link wie ?photo=3 öffnet die Galerie an dieser Slide. Wird ein Link geschlossen, der mit der Seite ankam, entfernt das den Parameter an Ort und Stelle, statt von deiner Website weg zu navigieren.
  • Zurück schließt nur, wenn du aus der App heraus geöffnet hast: Der Link hat einen Eintrag angelegt, also führt Zurück wieder zur Galerie. Ein geteilter Link, der direkt in einem frischen Tab geöffnet wird, hat keinen Verlauf hinter sich, daher verlässt Zurück im Browser die Website; der Schließen-Button oder Escape entfernt den Parameter an Ort und Stelle und lässt dich auf der Galerie. Die mitgelieferten Router-Adapter bestätigen einen Link-Push auf derselben Seite; ein eigener Adapter, der nicht sagen kann, wie der Eintrag zustande kam, bekommt ebenfalls das Schließen an Ort und Stelle. Das hinterlässt eine Kopie der Seite im Verlauf: Ein Schritt zurück sieht dann aus, als wäre nichts passiert, aber es öffnet sich nichts wieder.
  • Ein Parameter, der keine Slide benennt (ein veraltetes Lesezeichen, ein von Hand geänderter Wert), wird aus der URL entfernt, statt in der Adressleiste eine Slide zu behaupten, die sich nicht öffnen lässt.

In einer App mit Router übergibst du einen Adapter. Wer direkt in den Verlauf schreibt, lässt den Router mit einer veralteten Location zurück, und seine nächste Navigation verwirft den Parameter.

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} />

Öffnen ist ein Link. Weil in der URL steht, ob die Galerie offen ist, ist ein Vorschaubild ein ganz normaler Link ohne Klick-Handler, und das Verhalten des Browsers gibt es gratis dazu: in einem neuen Tab öffnen, die Adresse kopieren, beim Überfahren eine Vorschau sehen. In einer App mit Router nimmst du den Link des Routers, damit alles clientseitig bleibt.

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} />

Nimm für teilbare Links eine stabile Identität. Der Index ist eine Position, daher öffnet ein gespeichertes ?photo=3 ein anderes Bild, sobald die Liste umsortiert wird. urlStableIdKey verwendet die stabile id jedes Elements und durchsucht die aktuelle Liste: Ein einziger Aufruf deckt den üblichen Fall ab.

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

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

Übergib hashCodec: base64UrlCodec, um die id in der URL als base64url zu kodieren. Das ist eine umkehrbare Verschleierung, kein kryptografischer Hash.

Für ein anderes Feld als Schlüssel (einen slug) oder zum Nachladen eines endlosen Feeds mit locateAsync baust du codec/locator selbst:

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} />

Endlose oder seitenweise geladene Galerien. Das synchrone locate kann nur für bereits geladene Bilder antworten: Ein geteilter Link auf Bild 400 eines Feeds, der erst 20 geladen hat, findet nichts. locateAsync ist der Fallback und wird nur aufgerufen, wenn locate nichts findet.

Abkürzung

Ist die id des Elements dein Schlüssel? Dann spar dir den selbst geschriebenen Codec und Locator und übergib locateAsync direkt an urlStableIdKey({ items, locateAsync }) (es lädt nach, wenn nichts gefunden wird, und gibt dann den Index zurück). Die ausführliche Version unten ist für einen anderen Schlüssel oder für die volle Kontrolle gedacht.

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} />
  • Wie du nachlädst, entscheidest du: Lade zusammenhängende Seiten bis zum Ziel oder nur das eine Bild und hänge es an. Die URL verwendet die Identität, nicht die Position, daher gibt findIndex zurück, wo auch immer das Element landet.
  • Solange locateAsync läuft, bleibt die Lightbox geschlossen und der Parameter unangetastet, damit der Deep Link das Laden übersteht. null oder eine Ablehnung entfernt den Parameter.
  • Eine Antwort, die erst eintrifft, nachdem sich die URL geändert hat, nach dem Schließen oder nach dem Unmount, wird verworfen: Ein langsames Laden kann keine Slide öffnen, nach der niemand gefragt hat.
  • Während des Wartens wird nichts gerendert; die Seite hat diesen Ladezustand ohnehin schon, also rendere dein eigenes Skeleton.
  • Es gibt kein Timeout: Die Lightbox kann nicht wissen, wie lang die Galerie ist. Antworte mit null, wenn keine Seiten mehr kommen, sonst bleibt das Overlay auf Dauer geschlossen.
  • Was locateAsync zurückgibt, gilt: der Index der gerade geladenen Daten, unverändert übernommen, ohne images noch einmal zu lesen.

Automatisches Weiterblättern

Setze autoAdvance, um die Galerie beim Öffnen als Diashow abzuspielen. Standardmäßig ist es aus, sodass eine bestehende Lightbox wie bisher rendert und sich wie bisher verhält.

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

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  autoAdvance
  autoAdvanceInterval={4000}
/>;
  • Bilder bleiben für autoAdvanceInterval stehen (standardmäßig 5000 ms), gezählt ab dem Moment, in dem das Bild geladen ist, sodass ein langsames Bild nie übersprungen wird, bevor es jemand gesehen hat. Bei einem Bild, das nicht lädt, zählt die Zeit ab dem Fehler.
  • Videos laufen einmal, statt zu loopen, und es geht weiter, wenn sie enden. Mit useVideoSlideRenderer ist das für dich verdrahtet; ein eigenes renderSlide liest autoAdvance aus seinen Props und meldet das Ende über onEnded.
  • Es wartet, solange die Maus auf einem Control ruht (einem Button, einem Link oder der Bildunterschrift), während einer Wischbewegung und solange die Seite verborgen ist, und läuft dann dort weiter, wo es aufgehört hat.
  • Blätterst du von Hand (Pfeile, Tasten, Mausrad, Wischen), beginnt das Intervall der neuen Slide bei null; die Diashow bleibt an.
  • Auf der letzten Slide hält es an, außer loop ist gesetzt. Der Play/Pause-Button in den Controls zeigt den Zustand und lässt die Betrachtenden umschalten.

Ein eigenes renderControls bekommt isAutoAdvancing und, nur wenn autoAdvance an ist, onToggleAutoAdvance. Rendere einen PlayPauseButton, wenn es vorhanden ist. Das Timing selbst übernimmt createSlideshowController aus @reelkit/core; im Guide zu Autoplay baust du dasselbe Verhalten in deinen eigenen Player ein, und die Core API beschreibt seine Methoden.

Download

Setze showDownload, um den Controls einen Download-Button hinzuzufügen. Er speichert das aktive Element von seiner downloadUrl, ersatzweise von src, unter seinem downloadName, ersatzweise unter dem letzten Abschnitt der URL.

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
/>;

Die Datei wird geladen und gespeichert, weil ein Browser das Attribut download bei einem Link auf einen anderen Origin ignoriert und die Datei stattdessen öffnen würde. Schlägt das Laden fehl (meist ein Server auf einem anderen Origin, der keine CORS-Header sendet), öffnet sich die Datei in einem neuen Tab, wo man sie trotzdem speichern kann. Liefere die Dateien mit Access-Control-Allow-Origin aus, damit ein Klick zum Speichern reicht. Während ein Download läuft, ist der Button deaktiviert.

Ein eigenes renderControls bekommt isDownloading und, nur wenn showDownload an ist, onDownload. Rendere einen DownloadButton, wenn es vorhanden ist. Die Hilfsfunktion ist downloadFile aus @reelkit/core; mehr dazu im Guide zu Downloads.

API-Referenz

Props von LightboxOverlay

LightboxOverlayProps

PropTypStandardBeschreibung
isOpenbooleanPflichtSteuert die Sichtbarkeit der Lightbox. Soll die URL steuern, ob sie offen ist, nimm stattdessen die eigene Komponente LightboxUrlOverlay; siehe den URL-State weiter oben.
imagesLightboxItem[]PflichtArray der anzuzeigenden Bilder
ariaLabelstring'Image gallery'Zugängliche Beschriftung des Dialogbereichs; Screenreader lesen sie vor, wenn sich die Lightbox öffnet
initialIndexnumber0Index des ersten Bildes
transitionFnTransitionTransformFnslideTransitionFunktion für den Slide-Übergang. Importiere eine eingebaute (slideTransition, flipTransition, lightboxFadeTransition, lightboxZoomTransition) oder übergib eine eigene. Ohne Angabe gilt slideTransition.
autoAdvancebooleanfalseSpielt die Galerie als Diashow ab. Siehe Automatisches Weiterblättern.
autoAdvanceIntervalnumber5000Wie lange jedes Bild in einer Diashow stehen bleibt, in Millisekunden, gezählt ab dem Laden
showDownloadbooleanfalseFügt einen Download-Button für das aktive Element hinzu. Siehe Download.
apiRefMutableRefObject<ReelApi>-Ref für den Zugriff auf die Reel-API
renderControls(props: ControlsRenderProps) => ReactNode-Eigene Controls, ersetzt den Standard-Schließen-Button, den Zähler und den Vollbild-Schalter
renderNavigation(props: NavigationRenderProps) => ReactNode-Eigene Navigation, ersetzt die Standard-Pfeile für zurück und weiter
renderInfo(props: InfoRenderProps) => ReactNode-Eigenes Info-Overlay, ersetzt den Standard-Verlauf mit Titel und Beschreibung. Gib null zurück, um es auszublenden.
renderSlide(props: SlideRenderProps) => ReactNode | null-Eigenes Rendering der Slide. Bekommt { item, index, size, isActive, autoAdvance, onReady, onWaiting, onError, onEnded }. Gib null zurück, um auf den Standard zurückzufallen.
renderLoading(props: { item: LightboxItem; activeIndex: number }) => ReactNode-Eigene Ladeanzeige, ersetzt den Standard-Spinner
renderError(props: { item: LightboxItem; activeIndex: number }) => ReactNode-Eigene Fehleranzeige, ersetzt das Standard-Fehlersymbol

Props von LightboxUrlOverlay

LightboxUrlOverlayProps

Nimmt jedes Darstellungs- und Verhaltens-Prop von oben außer isOpen und ersetzt es durch einen controller. initialIndex wird hier ignoriert: Die Position des Controllers bestimmt die Slide, daher würde ein zusätzlich übergebener Wert bei jedem Öffnen überschrieben.

PropTypStandardBeschreibung
controllerUrlStateControllerPflichtController aus useOverlayUrlState. Seine Position entscheidet, ob das Overlay offen ist und welche Slide es zeigt; das Overlay schreibt beim Slide-Wechsel und beim Schließen über ihn zurück.

Callbacks

PropTypBeschreibung
onClose() => voidWird aufgerufen, wenn sich die Lightbox schließt. Erforderlich bei LightboxOverlay (du steuerst, ob sie offen ist, also musst du das Schließen behandeln); optional bei LightboxUrlOverlay, wo die URL das Schließen steuert. Übergib es dort nur, um nach dem Schließen zu reagieren.
onSlideChange(index: number) => voidWird nach einem Slide-Wechsel aufgerufen

Reel-Props (durchgereicht)

Diese Props werden an die zugrunde liegende Komponente Reel weitergegeben. Ihr Typ ist ReelProxyProps, die Teilmenge der Props von Reel, die das Overlay durchreicht; typisiere damit einen Wrapper, der sie weitergibt.

PropTypStandardBeschreibung
loopbooleanfalseAktiviert die Endlosschleife
enableNavKeysbooleantrueAktiviert die Tastaturnavigation
enableWheelbooleantrueAktiviert die Navigation mit dem Mausrad
wheelDebounceMsnumber200Debounce-Dauer für das Mausrad (ms)
transitionDurationnumber300Dauer der Übergangsanimation (ms)
swipeDistanceFactornumber0.12Schwelle für das Wischen (0-1)
swipeToCloseDirection'up' | 'down''up'Richtung der Geste zum Schließen auf Mobilgeräten

Typen

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;
}

Unterkomponenten

Wiederverwendbare Unterkomponenten, mit denen du über renderControls eigene Controls zusammenstellst.

CloseButton

Standard-Schließen-Button mit X. Props (CloseButtonProps): onClick, optional className und style.

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

<CloseButton onClick={onClose} />

Counter

Zähler für die Bilder als Pille, zeigt "1 / 3". Props (CounterProps): currentIndex (ab null gezählt), count, optional className und style.

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

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

FullscreenButton

Schalter für das Vollbild (Icon Maximize/Minimize). Props (FullscreenButtonProps): isFullscreen, onToggle, optional className und style.

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

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

SoundButton

Schalter für den Ton auf Video-Slides (Icon Volume2/VolumeX). Ist in renderControls aus useVideoSlideRenderer automatisch enthalten. Für den eigenständigen Einsatz in eigenen Controls greifst du über useSoundState auf den Tonzustand zu. Props (SoundButtonProps): isMuted, onToggle, optional className und style.

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

Schalter für die Diashow (Icon Play/Pause, aria-pressed während der Wiedergabe). Props (PlayPauseButtonProps): isPlaying, onToggle, optional className und style. Die Standard-Controls zeigen ihn, wenn autoAdvance an ist.

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

Download-Button (Icon Download), deaktiviert während isDownloading. Props (DownloadButtonProps): isDownloading, onClick, optional className und style. Die Standard-Controls zeigen ihn, wenn showDownload an ist.

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 für die optionale Video-Unterstützung. Gibt renderSlide, renderControls und SoundProvider zurück: Umschließe das Overlay mit SoundProvider und übergib die Render-Funktionen. Der Rückgabetyp ist UseVideoSlideRendererResult, der außerdem hasVideo enthält, true, wenn ein Element type: 'video' hat.

Sein renderSlide zeichnet Video-Elemente mit LightboxVideoSlide, einem Video-Slide, der über ein gemeinsames <video>-Element abspielt, damit der Ton auf iOS funktioniert, und sich die Position jedes Videos merkt. Rendere ihn nur aus einem eigenen renderSlide selbst. Seine Props (LightboxVideoSlideProps) sind src, poster, isActive, size, slideKey (Schlüssel für die gespeicherte Position und das aufgenommene Bild), loop (Standard true) und die 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 für den Vollbild-Zustand mit browserübergreifender Unterstützung.

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>
  );
}

Transitions

Übergib eine beliebige TransitionTransformFn über das Prop transitionFn. Importierst du nur die Transition, die du nutzt, kann der Bundler den Rest per Tree-Shaking entfernen. Ohne Angabe gilt slideTransition.

FunktionAusBeschreibung
slideTransition@reelkit/react-lightboxNormaler horizontaler Slide (Standard)
lightboxFadeTransition@reelkit/react-lightboxÜberblendung zwischen den Bildern
flipTransition@reelkit/react-lightboxEffekt einer umgedrehten 3D-Karte
lightboxZoomTransition@reelkit/react-lightboxZoomt von kleiner auf normale Größe
tsx
import {
  LightboxOverlay,
  lightboxFadeTransition,
} from '@reelkit/react-lightbox';

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

Eigene Transition-Funktion

Schreib deine eigene TransitionTransformFn und übergib sie über transitionFn. Die Signatur entspricht den Transitions des Core-Sliders.

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)}
/>

CSS-Klassen

Alle UI-Elemente verwenden einfache CSS-Klassen (keine CSS Modules), die du mit spezifischeren Selektoren in einem Stylesheet ansprechen kannst, das nach @reelkit/react-lightbox/styles.css geladen wird. Für Änderungen an Farbe, Größe und z-index nimm lieber die CSS Custom Properties, die unten im Abschnitt Theming beschrieben sind.

KlasseKomponenteBeschreibung
.rk-lightbox-overlayOverlayRoot-Container (Vollbild-Hintergrund)
.rk-lightbox-spinnerOverlayStandard-Spinner beim Laden
.rk-lightbox-img-errorOverlayContainer für den Fehlerzustand (kaputtes Bild oder Video)
.rk-lightbox-img-error-textOverlayTextlabel für den Fehlerzustand
.rk-lightbox-swipe-hintOverlayWisch-Hinweis auf Mobilgeräten
.rk-lightbox-controls-leftControlsContainer der Controls oben links
.rk-lightbox-btnControlsButtons der Controls (Vollbild und so weiter)
.rk-lightbox-play-pauseControlsPlay/Pause-Button der Diashow (mit .rk-lightbox-btn)
.rk-lightbox-downloadControlsDownload-Button (mit .rk-lightbox-btn)
.rk-lightbox-closeControlsSchließen-Button
.rk-lightbox-counterControlsChip mit dem Bildzähler
.rk-lightbox-navNavigationNavigationspfeile (beide)
.rk-lightbox-nav-prevNavigationPfeil zurück
.rk-lightbox-nav-nextNavigationPfeil weiter
.rk-lightbox-infoInfoContainer für Titel und Beschreibung
.rk-lightbox-titleInfoTitel des Bildes
.rk-lightbox-descriptionInfoBeschreibung des Bildes
.rk-lightbox-slideSlideContainer der Slide
.rk-lightbox-imgSlideBild-Element
.rk-lightbox-video-containerVideoSlideContainer der Video-Slide (optional)
.rk-lightbox-video-elementVideoSlideVideo-Element (optional)
.rk-lightbox-video-posterVideoSlidePosterbild des Videos (optional)

Theming

Jede Farbe, Größe, jeder z-index und jede Transition steckt in einer CSS Custom Property. Überschreibe eine oder mehrere auf :root (oder einem beliebigen Vorfahren der Lightbox), um das Aussehen zu ändern, ohne den Quellcode der Komponente anzufassen.

TokenStandardSteuert
--rk-lightbox-overlay-bg#000Farbe des Vollbild-Hintergrunds
--rk-lightbox-overlay-z9999z-index des Overlays
--rk-lightbox-top-shade-height80pxHöhe des Verlaufs oben
--rk-lightbox-top-shade-bglinear-gradient(rgba(0,0,0,0.6), transparent)Farbe des Verlaufs oben
--rk-lightbox-edge-padding16pxRandabstand für Schließen, Navigation und die Controls oben links
--rk-lightbox-controls-gap12pxAbstand zwischen den Controls oben links
--rk-lightbox-transition0.2sDauer der Hover-Transition der Buttons
--rk-lightbox-blur8pxRadius der Hintergrund-Unschärfe für Buttons und Chips
--rk-lightbox-btn-bgrgba(0, 0, 0, 0.5)Standard-Hintergrund für Schließen, Navigation und kleine Buttons
--rk-lightbox-btn-bg-hoverrgba(255, 255, 255, 0.2)Hover-Hintergrund für Schließen, Navigation und kleine Buttons
--rk-lightbox-btn-fg#fffIcon-Farbe für Schließen, Navigation und kleine Buttons
--rk-lightbox-btn-size36pxGröße der kleinen Buttons (Vollbild-Schalter und so weiter)
--rk-lightbox-btn-disabled-opacity0.5Deckkraft eines deaktivierten kleinen Buttons (laufender Download)
--rk-lightbox-close-size40pxGröße des Schließen-Buttons
--rk-lightbox-nav-size48pxGröße der Pfeile für zurück und weiter
--rk-lightbox-nav-opacity0.7Deckkraft der Pfeile im Ruhezustand
--rk-lightbox-counter-fg#fffTextfarbe des Zählers
--rk-lightbox-counter-bgrgba(0, 0, 0, 0.5)Hintergrund des Zähler-Chips
--rk-lightbox-counter-size14pxSchriftgröße des Zählers
--rk-lightbox-counter-padding6px 12pxInnenabstand des Zähler-Chips
--rk-lightbox-counter-radius20pxEckenradius des Zähler-Chips
--rk-lightbox-spinner-size28pxBreite und Höhe des Standard-Spinners
--rk-lightbox-spinner-trackrgba(255, 255, 255, 0.2)Farbe der Spinner-Spur
--rk-lightbox-spinner-fg#fffFarbe der Spinner-Anzeige
--rk-lightbox-spinner-duration0.8sDauer einer Spinner-Umdrehung
--rk-lightbox-error-fgrgba(255, 255, 255, 0.4)Farbe von Fehlersymbol und Fehlertext
--rk-lightbox-error-text-size13pxSchriftgröße der Fehlermeldung
--rk-lightbox-info-bglinear-gradient(transparent, rgba(0,0,0,0.8))Verlauf hinter der Bildunterschrift
--rk-lightbox-info-padding24pxInnenabstand der Bildunterschrift
--rk-lightbox-title-size18pxSchriftgröße des Titels
--rk-lightbox-description-size14pxSchriftgröße der Beschreibung
--rk-lightbox-info-fg#fffTextfarbe der Bildunterschrift
--rk-lightbox-hint-fgrgba(255, 255, 255, 0.5)Textfarbe des Wisch-Hinweises
--rk-lightbox-hint-bgrgba(0, 0, 0, 0.3)Hintergrund des Chips mit dem Wisch-Hinweis
--rk-lightbox-hint-duration3sGesamtdauer für das Ein- und Ausblenden des Wisch-Hinweises
--rk-lightbox-video-bg#000Letterbox-Hintergrund hinter <video>

Füge das Snippet unten in ein Stylesheet ein, das nach @reelkit/react-lightbox/styles.css geladen wird.

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)
  );
}

Barrierefreiheit

Die Wurzel des Overlays ist ein modaler Dialog (role="dialog", aria-modal="true"). Mit ariaLabel änderst du, was der Screenreader ansagt; standardmäßig ist es "Image gallery". Jede Slide trägt role="group", aria-roledescription="slide" und aria-label="Image N of M".

Die Lightbox holt beim Öffnen den Fokus und gibt ihn beim Schließen an den Auslöser zurück. Tab und Umschalt+Tab wechseln zwischen den fokussierbaren Elementen darin; Fokus, der entwischt (ein Klick daneben, ein Fokus per Code), wird zurückgeholt. Umgesetzt mit captureFocusForReturn und createFocusTrap aus @reelkit/core.

Tastenkürzel

TasteAktion
ArrowLeftVorheriges Bild
ArrowRightNächstes Bild
EscapeSchließt die Lightbox (oder beendet das Vollbild, falls aktiv)