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.
Maintainers
Keywords
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-sliderController 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:
before-change— cancelable for requested navigation; callpreventDefault()to keep the current page. It is non-cancelable when removed slides or a smaller page count make the current page invalid.slide-change— the active page has changed.after-change— cleanup is complete. This event is still dispatched whenonSlideChangethrows.
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 upThese 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-hiddenandinert, 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.
respectReducedMotionprevents 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:a11yDeterministic 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 changeRun the repeatable Chromium benchmark after performance-sensitive changes:
npm run benchmarkIt 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.
