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-slider

v1.0.0

Published

Accessible, dependency-free, framework-agnostic carousel and slider with Web Component, TypeScript, touch, RTL, loop, grid, zoom, and plugins.

Readme

wts-slider

Dependency-free, accessible carousel built on DOM standards. Use the WtsSlider controller or the standards-based <wts-slider> custom element; there are no framework adapters or framework runtime dependencies.

The current development branch adds opt-in grid layouts, media zoom and playback coordination, scrollbar navigation, URL synchronization, and five visual effects. These unreleased capabilities are designed as tree-shakable controller plugins with independent subpaths; the core export and the Web Component remain framework-neutral.

Install

npm install wts-slider

Controller API

The controller enhances the target's element children and restores them when destroyed.

<div id="gallery">
  <img src="/one.jpg" alt="Mountain at sunrise">
  <img src="/two.jpg" alt="Forest path">
  <img src="/three.jpg" alt="Ocean cliffs">
</div>
import { WtsSlider } from 'wts-slider';

const slider = new WtsSlider('#gallery', {
  slidesPerView: 2,
  slidesPerGroup: 1,
  centeredSlides: true,
  peek: 32,
  gap: 16,
  direction: 'auto',
  drag: {
    threshold: 0.15,
    velocityThreshold: 0.45,
    resistance: 0.35,
  },
  loop: true,
  autoplay: {
    delay: 4000,
    pauseOnFocus: true,
    pauseOnHover: true,
    progress: true,
  },
  pagination: 'progress',
  wheel: true,
  lazyLoad: { preload: 1 },
  breakpoints: {
    640: { slidesPerView: 2 },
    1024: { slidesPerView: 3 },
  },
  onSlideChange(detail) {
    console.log(detail.activeIndex);
  },
});

slider.next();
slider.previous();
slider.goTo(2);
slider.goToSlide(4);
slider.setOptions({ effect: 'fade' });
slider.pause();
slider.play();
slider.destroy();

Breakpoints use the carousel container's width, not the browser viewport. The largest matching breakpoint is applied.

Web Component

Importing the element entry point registers <wts-slider>:

import 'wts-slider/element';
<wts-slider
  aria-label="Featured destinations"
  slides-per-view="auto"
  slides-per-group="1"
  orientation="horizontal"
  snap-align="center"
  gap="16"
  direction="rtl"
  drag
  free-drag
  free-drag-momentum
  free-drag-snap
  rewind
  pagination="fraction"
  wheel
  lazy-load
  live-region
  respect-reduced-motion
>
  <article>First slide</article>
  <article>Second slide</article>
  <article>Third slide</article>

  <span slot="previous" aria-hidden="true">Back</span>
  <span slot="next" aria-hidden="true">Forward</span>
</wts-slider>

Options that contain functions or responsive objects are assigned through the options property:

const element = document.querySelector('wts-slider');
element.options = {
  breakpoints: {
    720: { slidesPerView: 2 },
  },
  messages: {
    previousLabel: 'Previous destination',
    nextLabel: 'Next destination',
    visibleStatus: 'Showing {start}–{end} of {total} destinations',
  },
  onSlideChange: ({ activeIndex }) => console.log(activeIndex),
};

The element exposes next(), previous(), goTo(index), goToSlide(index), play(), pause(), refresh(), setOptions(options), and controller.

Boolean drag attributes accept the same HTML-friendly forms as the other boolean attributes: drag, drag="true", and drag="false". Use the options property for custom drag thresholds:

element.options = {
  direction: 'auto',
  drag: {
    enabled: true,
    threshold: 0.2,
    velocityThreshold: 0.5,
    resistance: 0.25,
  },
};

When autoplay is enabled, the slider renders a visible pause/resume button. Autoplay also pauses while focus is inside the carousel or the pointer is over it when the corresponding autoplay options are enabled. Set autoplay.progress to show a visual timer for the current interval.

Events

Both APIs dispatch bubbling, composed lifecycle events. A page change follows this order:

  1. before-change — cancelable for requested navigation; call preventDefault() to keep the current page. It is non-cancelable when removed slides or a smaller page count make the current page invalid.
  2. slide-change — the active page has changed.
  3. after-change — cleanup is complete. This event is still dispatched when onSlideChange throws.

The three change events include:

interface WtsSliderChangeDetail {
  activeIndex: number;
  previousIndex: number;
  pageCount: number;
  slidesPerView: number;
  totalSlides: number;
}

before-change adds the requested, unclamped page:

interface WtsSliderBeforeChangeDetail extends WtsSliderChangeDetail {
  requestedIndex: number;
}

Playback calls emit play and pause:

interface WtsSliderPlaybackDetail {
  activeIndex: number;
  pageCount: number;
  totalSlides: number;
}

Container observations emit resize after responsive options are applied:

interface WtsSliderResizeDetail {
  activeIndex: number;
  pageCount: number;
  previousSlidesPerView: number;
  slidesPerView: number;
  totalSlides: number;
  width: number;
}

activeIndex is a zero-based page index. A page displays slidesPerView slides whenever enough slides exist. When the final page is incomplete, it anchors to the last full window instead of rendering empty trailing slots. The custom element provides typed listeners for every event in TypeScript.

Options

| Option | Default | Description | | --- | --- | --- | | slidesPerView | 4 | Visible slides per page, or 'auto' for authored sizes | | orientation | 'horizontal' | Uses a horizontal or vertical movement axis | | slidesPerGroup | slidesPerView | Number of slides advanced per navigation step | | centeredSlides | false | Centers intermediate groups when an edge peek is visible | | peek | 0 | Space reserved for adjacent-slide previews in pixels | | gap | 10 | Gap between slides in pixels | | height | 300 | Slide height as pixels or a CSS length | | direction | 'auto' | 'auto', 'ltr', or 'rtl'; auto follows the host's computed direction | | drag | true | Boolean or live pointer-drag configuration | | freeDrag | false | Continuous drag, optionally with momentum and snapping | | snapAlign | 'start' | Aligns the selected slide to 'start', 'center', or 'end' | | navigation | true | Shows previous and next controls | | pagination | 'bullets' | false, true, 'bullets', 'fraction', or 'progress' | | autoHideNavigation | true | Hides unavailable edge controls | | loop | false | Wraps navigation at the first and last page | | rewind | false | Returns to the opposite edge without continuous looping | | wheel | false | Enables horizontal mouse-wheel and trackpad navigation | | lazyLoad | false | Activates nearby img[data-src] and img[data-srcset] media | | autoplay | false | Boolean or autoplay configuration | | effect | 'slide' | Slide transition effect | | initialIndex | 0 | Initial page index | | keyboard | true | Enables arrows, Home, and End while focused | | breakpoints | {} | Container-width option overrides | | ariaLabel | 'Carousel' | Accessible carousel label | | messages | English defaults | Partial localized label and status templates | | liveRegion | true | Enables polite visible-range announcements | | regionRole | 'region' | Uses a region or group root role | | respectReducedMotion | true | Prevents autoplay when reduced motion is requested | | plugins | [] | Framework-neutral lifecycle extensions | | virtual | false | Mounts content only around the visible logical window | | injectStyles | true | Injects default styles for the controller API | | onSlideChange | — | Runs after the active page changes |

Effects are slide, fade, horizontal-flip, vertical-flip, parallax, shuffle, and none. fade, both flip effects, parallax, and shuffle stack each page in stable columns while leaving the track unshifted. slide and none use the translating track layout; none disables its transition. Because centered peeks require that translating layout, centeredSlides and peek resolve to false and 0 for every stacked effect.

The drag configuration accepts:

| Property | Default | Description | | --- | --- | --- | | enabled | true | Enables mouse, pen, and touch dragging | | threshold | 0.15 | Distance required to change page; values through 1 are viewport fractions and larger values are pixels | | velocityThreshold | 0.45 | Release velocity in pixels per millisecond that changes page | | resistance | 0.35 | Movement retained at non-looping edges, from 0 to 1 |

Dragging updates the track continuously, smooths noisy release-velocity samples, then navigates or snaps back when the pointer is released. Edge resistance uses a bounded rubber-band curve rather than allowing unlimited overscroll. RTL reverses the track, arrow-key, and drag directions.

Orientation, auto sizing, free drag, and alignment

Vertical mode uses the same API and lifecycle events as horizontal mode. Arrow Up/Down and vertical wheel gestures become the primary inputs; RTL only changes horizontal behavior.

slider.setOptions({
  orientation: 'vertical',
  height: 480,
  slidesPerView: 3,
  snapAlign: 'center',
});

Use slidesPerView: 'auto' when slide dimensions come from your CSS. The controller measures the authored sizes and computes page starts from real geometry. Auto sizing and centered peeks are mutually exclusive; auto sizing wins during normalization.

#gallery > article { width: clamp(16rem, 42vw, 28rem); }
slider.setOptions({
  slidesPerView: 'auto',
  slidesPerGroup: 1,
  freeDrag: {
    enabled: true,
    momentum: true,
    snap: true,
  },
  snapAlign: 'end',
});

Free drag is available for translating slide and none effects in non-looping sliders. momentum projects release velocity with bounded deceleration; snap settles on the nearest computed page. Without snap, the continuous offset is retained. An opposite trackpad gesture immediately interrupts the current wheel lock instead of waiting for its timeout.

Grouping, centering, and rewind

slidesPerGroup is independent from slidesPerView, so a slider can show three cards while advancing one card at a time. peek reserves space at both viewport edges for adjacent-slide previews. Add centeredSlides to center intermediate groups in that space.

rewind makes Next at the last page return to the first page, and Previous at the first page return to the last. Unlike loop, it does not imply a continuously repeating track. When both options are supplied, loop behavior takes precedence.

Continuous loop transitions copy current form values, checkbox state, selection, nested scroll positions, canvas pixels, and muted video playback position into their short-lived inert snapshots. Starting a new drag cancels the transient layer immediately. Closed-shadow custom elements and cross-origin frames cannot be cloned faithfully; use a non-animated transition for those slide types.

Synchronized sliders and thumbnails

Connect two existing controller instances without introducing a framework adapter. Slide mode follows the logical leading slide; progress mode maps between sliders with different page counts.

const gallery = new WtsSlider('#gallery', { slidesPerView: 1 });
const details = new WtsSlider('#details', { slidesPerView: 2 });

const disconnectSync = gallery.syncWith(details, {
  bidirectional: true,
  mode: 'progress',
});

const detachThumbnails = gallery.attachThumbnails('#thumbnails');

// Optional early cleanup; destroy() also disconnects owned relationships.
detachThumbnails();
disconnectSync();

Thumbnail children use data-wts-slider-thumbnail="0" (zero-based). The active item receives aria-current="true" and is-active; both are restored when detached.

Virtual content

Virtual mode represents a large logical collection with a bounded set of slide shells around the visible window. Lightweight spacers preserve the logical geometry, so a 10,000-item collection does not create 10,000 DOM elements. It is exclusive with authored slide elements.

const virtualSlider = new WtsSlider('#results', {
  slidesPerView: 4,
  virtual: {
    count: 10_000,
    overscan: 2,
    renderSlide(index) {
      const card = document.createElement('article');
      card.textContent = `Result ${index + 1}`;
      return card;
    },
  },
});

renderSlide may return a Node or a string. overscan controls how many neighboring items stay mounted. Navigation, accessibility labels, synchronization, and totalSlides continue to use the logical count. Virtual mode uses fixed-size slides (an 'auto' request falls back to one slide per view) and defaults to fraction pagination to avoid generating thousands of pagination buttons; an explicit pagination option still wins.

Plugins

Plugins extend lifecycle behavior through the same controller in every environment:

const analytics = {
  name: 'analytics',
  init({ slider }) {
    console.log('ready', slider.totalSlides);
    return () => console.log('cleanup');
  },
  afterChange(_context, detail) {
    console.log('page', detail.activeIndex);
  },
};

const remove = slider.use(analytics);
// Or configure once: new WtsSlider('#gallery', { plugins: [analytics] });
remove();

Hooks are init, beforeChange, afterChange, refresh, optionsChange, and destroy. Plugin names are unique per slider. Returning false from beforeChange cancels a requested transition. Cleanup functions and destroy hooks run during removal and controller destruction.

Optional modules

The feature modules below are optional capabilities on the current development branch and are planned for a future release as independent package subpaths. Import only the modules a slider uses; import { WtsSlider } from 'wts-slider' does not include or re-export their factories.

Create a fresh stateful module instance for each slider. Reusing the same zoom, media, scrollbar, or URL plugin object across active sliders is rejected so one slider cannot take ownership of another slider's listeners or controls.

| Subpath | Runtime exports | Purpose | | --- | --- | --- | | wts-slider/grid | createGridPlugin | Multi-row or multi-column pages | | wts-slider/effects | createEffectsPlugin, coverflowEffect, cardsEffect, cubeEffect, creativeEffect, zoomOutEffect | Optional 3D and layered transitions | | wts-slider/zoom | createZoomPlugin | Zoom and pan active-slide media | | wts-slider/media | createMediaPlugin | Coordinate audio/video with navigation and autoplay | | wts-slider/scrollbar | createScrollbarPlugin | Draggable, keyboard-accessible page navigation | | wts-slider/url | createUrlNavigation | Hash, query-string, and History API synchronization | | wts-slider/css-scroll-snap | createCssScrollSnapPlugin | Native browser scrolling and snap points | | wts-slider/presets | heroPreset, productGalleryPreset, storiesPreset, mediaViewerPreset | Immutable reusable option recipes | | wts-slider/plugin-kit | definePlugin, composePlugins, validatePluginOptions | Typed third-party plugin authoring utilities | | wts-slider/diagnostics | createDiagnosticsPlugin | Opt-in configuration and layout diagnostics |

All modules use the existing WtsSliderPlugin lifecycle. Install them in the constructor or at runtime, and keep the cleanup returned by use() when the module may be removed before the slider is destroyed:

import { WtsSlider } from 'wts-slider';
import { createScrollbarPlugin } from 'wts-slider/scrollbar';

const slider = new WtsSlider('#gallery');
const removeScrollbar = slider.use(createScrollbarPlugin());

removeScrollbar(); // optional; slider.destroy() also cleans it up

These are controller plugins, not React, Vue, Svelte, or Angular adapters. A framework integration uses the same imports and lifecycle shown below. A Web Component can receive plugins through its options.plugins property.

Multi-row and grid pages

Grid pages require fixed geometry. Set slidesPerView and slidesPerGroup to rows * columns and use start alignment:

import { WtsSlider } from 'wts-slider';
import { createGridPlugin } from 'wts-slider/grid';

const slider = new WtsSlider('#catalog', {
  slidesPerView: 6,
  slidesPerGroup: 6,
  gap: 16,
  plugins: [
    createGridPlugin({
      rows: 2,
      columns: 3,
      fill: 'row', // or 'column'
      crossAxisGap: 12,
    }),
  ],
});

Grid mode is incompatible with slidesPerView: 'auto', virtual slides, free drag, centered slides, peeks, and non-start snap alignment. The core effect must be slide or none. The plugin supports both slider orientations and restores its attributes, generated styles, and per-slide placement on cleanup.

Optional effects

The effects entry exposes a general factory and named convenience factories:

import { WtsSlider } from 'wts-slider';
import {
  cardsEffect,
  coverflowEffect,
  creativeEffect,
  cubeEffect,
  zoomOutEffect,
} from 'wts-slider/effects';

const slider = new WtsSlider('#showcase', {
  effect: 'slide',
  slidesPerView: 3,
  slidesPerGroup: 1,
  plugins: [coverflowEffect({ rotate: 36, depth: 140, scale: 0.88 })],
});

Available module effects are coverflow, cards, cube, creative, and zoom-out. They retain core slide geometry, require the core slide effect, and cannot be combined with free drag. cards and cube additionally require one fixed, non-virtual slide per page, start alignment, no centering, and no peek. Module transitions honor reduced-motion preferences and restore all managed CSS properties when removed.

Zoom and media playback

Zoom targets [data-wts-slider-zoom], img, or video in the active slide by default. It provides focal two-pointer pinch zoom, geometry-bounded panning, optional controls, wheel scaling, double click, and +, -, 0, and Escape shortcuts:

import { createZoomPlugin } from 'wts-slider/zoom';

const removeZoom = slider.use(createZoomPlugin({
  selector: '[data-product-image]',
  minScale: 1,
  maxScale: 5,
  step: 0.5,
  controls: true,
}));

slider.root.addEventListener('wts-slider-zoom-change', (event) => {
  console.log(event.detail.scale, event.detail.slideIndex);
});

Zoom resets when the active slide changes. Cleanup restores authored media transforms, viewport touch behavior, accessibility attributes, and generated controls.

Use the separate media module when audio or video playback should follow slide visibility:

import { createMediaPlugin } from 'wts-slider/media';

slider.use(createMediaPlugin({
  pauseInactive: true,
  resetInactive: false,
  autoplayActive: false,
  pauseSliderAutoplay: true,
}));

autoplayActive attempts browser media playback and, by default, temporarily mutes autoplay-owned media. Browser autoplay policy can still reject the request. The module can pause slider autoplay while media plays and restores muted state and listeners on cleanup.

Scrollbar navigation

import { createScrollbarPlugin } from 'wts-slider/scrollbar';

slider.use(createScrollbarPlugin({
  ariaLabel: 'Choose product page',
  autoHide: true,
  wheel: true,
  // container: '#gallery-scrollbar',
}));

The generated scrollbar exposes native slider semantics and supports pointer dragging, Home, End, Page Up/Down, orientation-aware arrow keys, wheel input, vertical sliders, and horizontal RTL. A selector container is resolved from the slider's document or shadow root. Generated markup, styles, listeners, and any temporary root id are removed during cleanup.

URL and history synchronization

Slides use data-wts-slider-url, then their id, then their one-based index as the default stable URL token:

<article data-wts-slider-url="overview">Overview</article>
<article data-wts-slider-url="specifications">Specifications</article>
import { createUrlNavigation } from 'wts-slider/url';

slider.use(createUrlNavigation({
  mode: 'hash',       // or 'query'
  key: 'product',     // writes #product=specifications
  history: 'replace', // or 'push'
  readInitial: true,
}));

Hash mode also supports key: false for a plain #token. Give each slider a unique key when several share a page. The plugin responds to hashchange and popstate, and resolves window only after initialization, keeping the module safe to import during SSR. Virtual collections can provide serialize(index, context) and parse(token, context) because their target slide may not be mounted. Removing the plugin detaches both URL listeners.

Native CSS scroll snap

Use native scrolling when browser momentum and platform scroll behavior are preferred over transform-driven dragging:

import { createCssScrollSnapPlugin } from 'wts-slider/css-scroll-snap';

const slider = new WtsSlider('#gallery', {
  drag: false,
  loop: false,
  slidesPerGroup: 1,
  slidesPerView: 1,
  plugins: [
    createCssScrollSnapPlugin({
      behavior: 'smooth',
      snapStop: 'always',
    }),
  ],
});

The module supports horizontal, vertical, LTR, RTL, and start/center/end alignment. Native scroll settling updates controller state, while controller navigation scrolls the viewport to the selected slide. It requires one slide per view and group and is intentionally incompatible with loop, virtual slides, free drag, grid, and layered effects.

Presets

Presets are immutable option recipes. Application overrides and optional plugins remain explicit, so importing a preset never pulls optional modules into the core bundle:

import { WtsSlider } from 'wts-slider';
import { productGalleryPreset } from 'wts-slider/presets';
import { createZoomPlugin } from 'wts-slider/zoom';

const options = productGalleryPreset({
  options: { height: 520 },
  plugins: [createZoomPlugin()],
});
const slider = new WtsSlider('#product', options);

Available recipes are hero, productGallery, stories, and mediaViewer. Use createWtsSliderPreset(name, composition) for a dynamic preset picker.

Plugin authoring and diagnostics

The plugin kit normalizes stable names, validates frozen option snapshots, and can compose several lifecycle plugins behind one cleanup-safe plugin:

import {
  composePlugins,
  definePlugin,
  validatePluginOptions,
} from 'wts-slider/plugin-kit';

const analytics = definePlugin({
  name: 'analytics',
  afterChange(_context, detail) {
    console.log(detail.activeIndex);
  },
});
const modules = composePlugins('product-modules', [analytics]);

Development diagnostics are separate and tree-shakable:

import { createDiagnosticsPlugin } from 'wts-slider/diagnostics';

slider.use(createDiagnosticsPlugin({
  reporter(diagnostic) {
    console.warn(diagnostic.code, diagnostic.message);
  },
  strict: false,
}));

Diagnostics detect missing navigation input, ineffective autoplay, duplicate plugin names, slide IDs or URL tokens, zero-size viewports, and tracks that do not overflow despite multiple pages. Reports can also be consumed from the wts-slider-diagnostic DOM event.

Nested sliders

Nested controllers need no module. Pointer, wheel, keyboard, pagination, and shadow-DOM composed events from a child slider are ignored by ancestor sliders, so each controller owns only interactions originating within its own root.

Pagination

Choose the pagination display without changing navigation semantics:

slider.setOptions({ pagination: 'bullets' });
slider.setOptions({ pagination: 'fraction' });
slider.setOptions({ pagination: 'progress' });

true is an alias for 'bullets'. Every mode remains labeled for assistive technology; reduced-motion preferences disable animated progress.

Lazy media

Keep the initial request small by putting deferred image URLs in data-src or data-srcset:

<img
  data-src="/photos/coast-1280.jpg"
  data-srcset="/photos/coast-640.jpg 640w, /photos/coast-1280.jpg 1280w"
  sizes="(min-width: 900px) 50vw, 100vw"
  alt="Coastal cliffs"
>
new WtsSlider('#gallery', {
  lazyLoad: { preload: 1 },
});

The visible group is activated immediately. preload controls how many neighboring slides are activated around it. Loaded image URLs are left in place when lazy loading is later disabled.

Accessibility

  • The root is exposed as a labeled carousel region and each slide is announced with its position.
  • Bullet pagination uses a roving tab stop. Arrow keys, Home, and End move and activate a bullet without adding every page to the tab order.
  • Inactive slides are aria-hidden and inert, so their interactive descendants stay out of keyboard navigation.
  • Arrow, Home, and End navigation is scoped to the focused carousel and is ignored inside text-editing controls.
  • Pointer gestures do not start from links, buttons, form controls, or editable content.
  • Wheel navigation only consumes gestures on the configured orientation axis, leaving perpendicular page scrolling available.
  • Autoplay has a visible pause/resume control and honors its focus and hover pause settings.
  • respectReducedMotion prevents autoplay scheduling when the operating system requests reduced motion.
  • A visually hidden live region announces the visible range after manual navigation. It is aria-live="off" while autoplay is moving and returns to polite announcements when paused.
  • Direction-aware keyboard and control behavior supports both LTR and RTL documents.
  • Forced-colors styles retain visible control boundaries, active pagination, and focus indication.

Set regionRole: 'group' when the carousel is already inside a labeled landmark. Set liveRegion: false when the surrounding application owns its announcements.

See the accessibility testing guide for VoiceOver, NVDA, TalkBack, forced-colors, and 200% zoom certification steps.

All generated text is localizable with messages. Templates safely replace {current}, {total}, {start}, and {end}:

slider.setOptions({
  messages: {
    previousLabel: 'Vorherige Seite',
    nextLabel: 'Nächste Seite',
    startAutoplayLabel: 'Automatisch abspielen',
    pauseAutoplayLabel: 'Automatik pausieren',
    paginationLabel: 'Seite auswählen',
    pageLabel: 'Seite {current}',
    progressLabel: 'Seite {current} von {total}',
    slideLabel: 'Folie {current} von {total}',
    visibleStatus: 'Folien {start} bis {end} von {total}',
  },
});

Styling

The controller injects its defaults unless injectStyles is false. They can also be imported explicitly:

import 'wts-slider/styles.css';

Theme with CSS custom properties:

#gallery,
wts-slider {
  --wts-slider-accent: #2563eb;
  --wts-slider-navigation-background: rgb(15 23 42 / 75%);
  --wts-slider-navigation-color: white;
  --wts-slider-pagination-color: #94a3b8;
  --wts-slider-progress-background: rgb(148 163 184 / 35%);
  --wts-slider-transition: 300ms ease;
}

The Web Component exposes the CSS parts root, viewport, track, navigation-previous, navigation-next, autoplay-toggle, pagination, pagination-button, autoplay-progress, and autoplay-progress-fill. Slides remain in light DOM and can be styled with wts-slider > :not([slot]).

Framework lifecycle

Create the controller after the host has rendered and call destroy() during the framework's unmount or destroy lifecycle. These are integration examples, not adapters; every example imports the same controller.

React

import React from 'react';
import { WtsSlider } from 'wts-slider';

function Gallery() {
  const host = React.useRef<HTMLDivElement>(null);
  React.useEffect(() => {
    const slider = new WtsSlider(host.current!, { slidesPerView: 2 });
    return () => slider.destroy();
  }, []);
  return <div ref={host}><article>One</article><article>Two</article></div>;
}

Vue

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { WtsSlider } from 'wts-slider';
const host = ref<HTMLElement>();
let slider: WtsSlider;
onMounted(() => slider = new WtsSlider(host.value!, { slidesPerView: 2 }));
onBeforeUnmount(() => slider.destroy());
</script>
<template><div ref="host"><article>One</article><article>Two</article></div></template>

Svelte

<script lang="ts">
  import { onMount } from 'svelte';
  import { WtsSlider } from 'wts-slider';
  let host: HTMLElement;
  onMount(() => {
    const slider = new WtsSlider(host, { slidesPerView: 2 });
    return () => slider.destroy();
  });
</script>
<div bind:this={host}><article>One</article><article>Two</article></div>

Angular

import {
  afterNextRender,
  DestroyRef,
  ElementRef,
  inject,
  viewChild,
} from '@angular/core';
import { WtsSlider } from 'wts-slider';

private readonly host = viewChild.required<ElementRef<HTMLElement>>('host');
private readonly destroyRef = inject(DestroyRef);

constructor() {
  afterNextRender(() => {
    const slider = new WtsSlider(this.host().nativeElement, {
      slidesPerView: 2,
    });
    this.destroyRef.onDestroy(() => slider.destroy());
  });
}

Any framework can instead render <wts-slider> after importing wts-slider/element. Set complex options through the element's options property and let normal DOM removal trigger its lifecycle. Angular requires CUSTOM_ELEMENTS_SCHEMA; React TypeScript projects may declare wts-slider in JSX.IntrinsicElements. Neither is a package adapter.

Interactive playground

The repository demo exposes an interactive playground at the /wts-slider route. It lets users change layout, direction, dragging, effects, autoplay, grouping, centered peeks, rewind, wheel input, lazy loading, navigation, pagination modes, accessibility messaging, responsive options, auto sizing, orientation, free-drag momentum, and alignment while seeing live state, events, and generated configuration. The same route includes a synchronized slider, clickable thumbnails, and a separate 100-item virtual example. Its advanced lab also switches presets, native CSS scroll snap, pinch zoom, diagnostics, and a nested child slider while generating the exact optional subpath imports.

See MIGRATION.md when upgrading from the Angular package. For searchable reference material, see the published API reference, compatibility matrix, plugin-authoring guide, troubleshooting guide, and physical-device checklist. Before preparing an artifact, follow the release checklist.

Quality checks

The package test suite includes unit, packaged-artifact, and Playwright browser coverage. Browser tests run against Chromium, Firefox, and WebKit. Automated WCAG A/AA checks use axe against both the controller and Web Component in initial and navigated states:

npm test
npm run size:check
npm run api:check
npm run test:package
npm run test:install
npm run test:browser
npm run test:a11y

Deterministic visual regression baselines cover representative controller and Web Component states in Chromium:

npm run test:visual
npm run test:visual:update # intentionally accept a reviewed visual change

Run the repeatable Chromium benchmark after performance-sensitive changes:

npm run benchmark

It reports median creation, navigation, refresh, and teardown time for 100 and 1,000 authored slides and up to 10,000 virtual records. Teardown also verifies that authored DOM is restored and virtual ownership leaves no nodes behind. The readable budgets in benchmark/benchmark.mjs are regression guards, not claims about every consumer device; compare results on consistent hardware and CI runners.