npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wts-image-gallery

v1.0.1

Published

Accessible, dependency-free, framework-agnostic image gallery and lightbox for TypeScript, Web Components and modern frameworks.

Readme

wts-image-gallery

npm version npm downloads license bundle size

A dependency-free, accessible image gallery and lightbox for plain JavaScript, Angular, React, Vue, Svelte, and other browser frameworks.

The package provides two framework-neutral APIs:

  • WtsImageGallery, a lifecycle-friendly TypeScript controller.
  • <wts-image-gallery>, an optional standards-based Web Component.

Install

npm install wts-image-gallery

Controller API

<div class="gallery" aria-label="Architecture gallery">
  <img
    src="/images/one.webp"
    alt="Concrete building"
    data-title="Concrete study"
    data-description="A concrete facade in afternoon light."
  >
  <img
    src="/images/two.webp"
    alt="Glass building"
    data-title="Glass study"
  >
</div>
import { WtsImageGallery } from 'wts-image-gallery';

const gallery = new WtsImageGallery('.gallery', {
  imageSelector: 'img',
  zoom: true,
  keyboardEnabled: true,
  fullScreenEnabled: true,
});

// Optional programmatic controls:
gallery.open(0);
gallery.next();
gallery.zoomIn();
gallery.panTo(20, 10);
gallery.close();

// Call this from the owning view's cleanup hook.
gallery.destroy();

Importing wts-image-gallery does not access window or document. Construct the controller after your view has mounted when rendering on a server.

Explicit items

Images can be supplied without scanning markup:

const gallery = new WtsImageGallery(document.querySelector('#trigger')!, {
  items: [
    {
      src: '/images/one.webp',
      placeholderSrc: '/images/one-blur.webp',
      thumbnailSrc: '/images/one-thumb.webp',
      srcset: '/images/one-1280.webp 1280w, /images/one-2560.webp 2560w',
      sizes: '100vw',
      alt: 'Concrete building',
      title: 'Concrete study',
      description: 'A concrete facade in afternoon light.',
      width: 2560,
      height: 1707,
    },
    {
      src: '/images/two.webp',
      alt: 'Glass building',
      title: 'Glass study',
    },
  ],
  loop: true,
});

gallery.open();

Modern lightbox features

Modern capabilities are progressive enhancements and remain dependency-free:

const gallery = new WtsImageGallery(element, {
  nativeDialog: true,
  viewTransitions: true,
  swipe: true,
  swipeThreshold: 48,
  filmstrip: { enabled: true, thumbnails: true, windowSize: 15 },
  preload: { before: 1, after: 2, cache: true },
  cacheSize: 12,
  urlSync: {
    key: 'gallery',
    id: 'architecture',
    mode: 'push',
  },
  direction: 'auto',
  locale: 'en',
  onBeforeAction: (detail) => {
    // Return false to cancel an open, close, navigation, or zoom action.
    console.log(detail.action);
  },
  onAfterAction: (detail) => console.log(detail.action),
  onRenderItem: ({ image, item }) => {
    image.dataset['assetId'] = item.src;
  },
});

nativeDialog uses <dialog>.showModal() when available and falls back to the package's accessible modal implementation. View Transitions are feature-detected and automatically fall back to an immediate update. Reduced-motion preferences disable nonessential transitions.

The active image supports placeholder-to-full-image loading, srcset, sizes, dimensions, CORS mode, fetch priority, decode(), stale-load protection, error state reporting, and bounded adjacent-image preloading. Set cache: false to release preload objects after use.

When urlSync is enabled, the current item is encoded in the query string. Browser Back closes or navigates the gallery, and an existing gallery URL can open the matching item during construction.

Web Component

Import the optional element entry point once:

import 'wts-image-gallery/element';

Then use standard HTML:

<wts-image-gallery
  loop
  zoom="true"
  native-dialog
  swipe
  filmstrip
  view-transitions
  direction="auto"
  locale="en"
>
  <img src="/images/one.webp" alt="Concrete building" data-title="Concrete study">
  <img src="/images/two.webp" alt="Glass building" data-title="Glass study">
</wts-image-gallery>

Boolean attributes accept an explicit false value:

<wts-image-gallery zoom="false" fullscreen-enabled="false">
  <!-- images -->
</wts-image-gallery>

The element exposes openAt(index), close(), next(), previous(), goTo(index), zoomIn(), zoomOut(), resetZoom(), setZoom(value), setZoomAt(value, x, y), panTo(x, y), toggleFullscreen(), refresh(), and useDocumentItems().

Declarative interaction attributes include wheel-zoom, double-click-zoom, pan-enabled, keyboard-enabled, close-on-escape, close-on-backdrop, fullscreen-enabled, loop, observe, preload, and inject-styles, plus native-dialog, swipe, swipe-threshold, filmstrip, filmstrip-window-size, view-transitions, cache, cache-size, direction, and locale. Structured urlSync, asymmetric preload, labels, callbacks, and render hooks can be assigned through the element's options property.

Framework lifecycle examples

Use the controller from the framework's normal mount and cleanup hooks. No framework adapter is required.

Angular

import {
  afterNextRender,
  Component,
  DestroyRef,
  ElementRef,
  inject,
  viewChild,
} from '@angular/core';
import { WtsImageGallery } from 'wts-image-gallery';

@Component({
  selector: 'app-gallery',
  template: `<div #images><img src="/one.webp" alt="One"></div>`,
})
export class GalleryComponent {
  private readonly images =
    viewChild.required<ElementRef<HTMLElement>>('images');
  private readonly destroyRef = inject(DestroyRef);

  constructor() {
    afterNextRender(() => {
      const gallery = new WtsImageGallery(this.images().nativeElement);
      this.destroyRef.onDestroy(() => gallery.destroy());
    });
  }
}

React

import { useEffect, useRef } from 'react';
import { WtsImageGallery } from 'wts-image-gallery';

export function Gallery() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const gallery = new WtsImageGallery(ref.current!);
    return () => gallery.destroy();
  }, []);

  return <div ref={ref}><img src="/one.webp" alt="One" /></div>;
}

Vue

<script setup lang="ts">
import { onBeforeUnmount, onMounted, useTemplateRef } from 'vue';
import { WtsImageGallery } from 'wts-image-gallery';

const images = useTemplateRef<HTMLElement>('images');
let gallery: WtsImageGallery | undefined;

onMounted(() => {
  gallery = new WtsImageGallery(images.value!);
});
onBeforeUnmount(() => gallery?.destroy());
</script>

<template>
  <div ref="images"><img src="/one.webp" alt="One"></div>
</template>

Options

| Option | Default | Purpose | | --- | --- | --- | | items | scanned from markup | Explicit image data | | imageSelector | img | Elements discovered inside the target | | startIndex | 0 | Initially selected image | | title | enabled, bottom | Title visibility and position | | description | enabled, bottom | Description visibility and position | | zoom | true | Zoom controls and shortcuts | | minZoom / maxZoom | 0.5 / 3 | Zoom limits | | zoomStep | 0.5 | Zoom increment | | wheelZoom | true | Mouse-wheel zoom | | doubleClickZoom | true | Toggle between 1× and 2× | | panEnabled | true | Drag and touch panning while zoomed | | keyboardEnabled | true | Arrow, Home, End, +, -, and 0 keys | | closeOnEscape | true | Escape key closes the dialog | | closeOnBackdrop | true | Backdrop click closes the dialog | | fullScreenEnabled | true | Fullscreen control | | loop | false | Wrap first/last navigation | | observe | false | Observe markup changes; the element defaults to true | | preload | true | Adjacent preload boolean, count, or { before, after, cache } | | cache / cacheSize | true / 12 | Retain prepared images in a bounded LRU cache | | nativeDialog | true | Prefer native modal dialog behavior with fallback | | swipe / swipeThreshold | true / 48 | Navigate with horizontal pointer or touch gestures | | filmstrip | false | Thumbnail navigation; accepts { enabled, thumbnails, windowSize } | | urlSync | false | Deep linking and browser-history synchronization | | viewTransitions | true | Feature-detected image/open transition enhancement | | direction | auto | LTR/RTL navigation and gesture direction | | locale | document language or en | Modal language metadata; pair with labels for translation | | injectStyles | true | Inject default scoped styles | | labels | English labels | Accessible label overrides |

Callbacks are available as onOpen, onClose, onChange, onZoom, and onError. Extension hooks are available as onBeforeAction, onAfterAction, and onRenderItem.

Events

The target dispatches bubbling, composed custom events:

  • wts-image-gallery-open
  • wts-image-gallery-close
  • wts-image-gallery-change
  • wts-image-gallery-zoom
  • wts-image-gallery-error
container.addEventListener('wts-image-gallery-change', (event) => {
  console.log((event as CustomEvent).detail);
});

Styling

Default styles are injected and scoped to the lightbox. Set CSS custom properties globally, on a programmatic portal, or on the custom element:

:root {
  --wts-gallery-z-index: 1200;
  --wts-gallery-overlay: rgba(7, 12, 24, 0.9);
  --wts-gallery-accent: #7c3aed;
  --wts-gallery-image-radius: 14px;
}

To manage styles yourself, use injectStyles: false and import:

import 'wts-image-gallery/styles.css';

The Web Component exposes stable CSS shadow parts for triggers, portal, lightbox, backdrop, dialog, toolbar, stage, figure, image, caption, title, description, status, live-region, filmstrip, filmstrip-track, thumbnail, thumbnail-current, and each control button. For example:

wts-image-gallery::part(thumbnail-current) {
  outline: 3px solid var(--brand-accent);
}

wts-image-gallery::part(caption) {
  backdrop-filter: blur(16px);
}

Accessibility

The lightbox uses native modal behavior where available, accessible fallback dialog semantics, labelled controls, a live image counter, focus trapping, focus restoration, keyboard and filmstrip navigation, RTL handling, forced-colors support, and reduced-motion preferences. Plain image triggers receive keyboard button semantics while mounted and are restored by destroy(). Mouse-wheel and focal-point double-click zoom, pointer dragging, two-pointer pinch gestures, and one-pointer swipe navigation are supported. Meaningful alt text remains the application's responsibility.

Browser support

Modern evergreen browsers supporting ES2020. Fullscreen is hidden when the browser does not expose the Fullscreen API.

The published runtime remains importable on Node.js 18 and newer for SSR. Package development and release commands require a supported Node.js 22.22+, 24.15+, or 26+ release because of the locked test toolchain.

Migrating from the Angular directive

Version 1 is a framework-neutral rewrite and intentionally does not export WtsImageDirective. See MIGRATION.md.

License

MIT