Compositor-driven transitions
Opening, closing, the clip reveal and slide-steps run on the compositor via the Web Animations API, so they hold their frame rate even while images decode.
A fast, type-safe, extensible lightbox — a dependency-free core with a first-class Svelte 5 adapter. Pinch to zoom, drag to dismiss, and transitions that stay smooth even while images decode.
min+gzip, icons included
the dependency-free core, min+gzip
runtime dependencies in the core
long tasks while navigating
Bundle figures are min+gzip; the long-task count is from picola’s benchmark harness.
Sensible defaults, a small footprint, and an escape hatch when you need one.
Opening, closing, the clip reveal and slide-steps run on the compositor via the Web Animations API, so they hold their frame rate even while images decode.
Open and close animate from the source thumbnail. For an object-fit: cover crop, the full image is revealed with an animated clip instead of a jump.
Only the active slide ±1 stays mounted; images outside the window are detached so the browser reclaims their decoded pixels.
Placeholder first, full resolution deferred to a zoomintent event, then swapped in with zoom and pan preserved. Reactive slides for pagination.
Focal-point pinch / double-tap / wheel zoom, iOS-style rubber-banding, velocity flicks, and drag- or pinch-to-dismiss with progress you can read from CSS.
Zero-specificity :where defaults and flat single-class selectors: override by normal cascade order — no !important, and no @layer to lose against your resets.
Toolbar and caption via snippets and real components; a generic Slide<T> carries your data through typed events. No SVG-string injection, no manual mount.
Zero dependencies, owning gestures, physics and transforms. The Svelte adapter is the reference build — write one for any framework.
Real picola, running in a Svelte island. Click any photo, then pinch, zoom, drag down to dismiss, or use your keyboard.
Install, import the stylesheet, pass your slides. Everything else is optional refinement.
<script lang="ts"> import { Lightbox, type Slide } from 'picola/svelte'; import 'picola/picola.css';
const slides: Slide[] = [ { src: '/photos/1.jpg', placeholder: '/photos/thumb/1.jpg', width: 2400, height: 1600, alt: 'A river running through a valley at dusk.' } ];
let open = $state(false);</script>
<button onclick={() => (open = true)}>Open gallery</button>
<Lightbox bind:open {slides} />import { createViewer } from 'picola';
const viewer = createViewer({ slides, startIndex: 0 });viewer.attach({ viewport, track, backdrop });
viewer.on('change', ({ index }) => console.log(index));viewer.open(0);Every control is tuned through CSS custom properties on the .pcl root — no !important, no fighting the cascade.
--pcl-backdrop--pcl-backdrop-filter--pcl-chrome-color--pcl-button-bg--pcl-caption-lines--pcl-arrow-size.pcl { --pcl-z: 2000; --pcl-backdrop: rgba(10, 10, 16, 0.55); --pcl-backdrop-filter: blur(16px); --pcl-chrome-color: #eafaff; --pcl-button-bg: rgba(92, 202, 233, 0.16); --pcl-caption-lines: 3;}Install picola and ship a polished image experience.