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

lazer-slider

v1.10.0

Published

A lightweight, accessible slider with smooth scroll-to-snap animations and drag-to-scroll support

Downloads

92

Readme

Lazer Slider

A lightweight, accessible slider with smooth scroll-to-snap animations, drag-to-scroll support, and full keyboard navigation.

Features

  • Smooth Animations - Buttery smooth scroll animations using requestAnimationFrame with customizable easing
  • Drag to Scroll - Mouse and touch drag support with momentum physics
  • Responsive - Separate settings for mobile and desktop (slidesPerView, slidesPerScroll)
  • Vertical & Horizontal - Support for both horizontal and vertical slider directions
  • Loop Mode - Infinite loop navigation
  • Autoplay - Automatic slide advancement with pause on hover
  • Auto-Scroll - Continuous smooth scrolling with drag interaction and seamless infinite loop
  • Automatic Bullets - Auto-generate navigation bullets from slides
  • Thumbs Gallery - Auto-generate thumbnail navigation from slide images
  • Accessible - Full ARIA support, keyboard navigation (arrow keys)
  • Optional CSS - Ready-to-use styles with CSS custom properties for easy theming
  • Lightweight - Zero dependencies, ~17KB minified
  • TypeScript - Full type definitions included

Installation

npm install lazer-slider

Quick Start

HTML Structure

<div class="lazer-slider">
  <button class="lazer-nav lazer-prev">‹</button>
  <button class="lazer-nav lazer-next">›</button>

  <div class="lazer-feed">
    <div class="lazer-slide">Slide 1</div>
    <div class="lazer-slide">Slide 2</div>
    <div class="lazer-slide">Slide 3</div>
  </div>

  <div class="lazer-bullets"></div>
</div>

CSS

Import the optional stylesheet for ready-to-use styles:

import 'lazer-slider/css'

Or in HTML:

<link rel="stylesheet" href="node_modules/lazer-slider/dist/lazer-slider.css">

Note: The CSS is optional. You can write your own styles or customize the included ones using CSS custom properties. See Styling for details.

JavaScript

import { createSlider } from 'lazer-slider'
import 'lazer-slider/css'

const slider = createSlider({
  feed: document.querySelector('.lazer-feed'),
  slides: [...document.querySelectorAll('.lazer-slide')],
  prevSlideButton: document.querySelector('.lazer-prev'),
  nextSlideButton: document.querySelector('.lazer-next'),
  bulletsContainer: document.querySelector('.lazer-bullets'),
  bulletsClass: 'lazer-bullet',
  enableDragToScroll: true
})

// Cleanup when done
slider.unload()

Configuration Options

Required

| Option | Type | Description | |--------|------|-------------| | feed | HTMLElement | Container element that holds the slides (scrollable) | | slides | HTMLElement[] | Array of slide elements |

Navigation

| Option | Type | Default | Description | |--------|------|---------|-------------| | prevSlideButton | HTMLElement \| null | null | Previous slide button | | nextSlideButton | HTMLElement \| null | null | Next slide button | | thumbs | HTMLElement[] | undefined | Thumbnail/dot elements for direct navigation (manual) | | bulletsContainer | HTMLElement \| null | null | Container element for auto-generated bullets | | bulletsClass | string | 'slider-bullet' | CSS class for bullet elements | | bulletsActiveClass | string | 'active' | CSS class for active bullet element | | thumbsContainer | HTMLElement \| null | null | Container element for auto-generated thumbnails | | thumbsClass | string | 'slider-thumb' | CSS class for thumbnail elements | | thumbsActiveClass | string | 'active' | CSS class for active thumbnail element | | thumbImageSelector | string | 'img' | CSS selector to find image inside each slide | | thumbSize | { width: number; height: number } | undefined | Fixed dimensions for thumbnail images |

Note: When bulletsContainer or thumbsContainer is provided, navigation elements are automatically generated. If thumbs is also provided, both containers are ignored. Elements are only generated for visible slides.

Responsive

| Option | Type | Default | Description | |--------|------|---------|-------------| | mobileSlidesPerView | number \| 'auto' | undefined | Slides visible at once on mobile | | desktopSlidesPerView | number \| 'auto' | undefined | Slides visible at once on desktop | | mobileSlidesPerScroll | number | 1 | Slides to scroll per nav click on mobile | | desktopSlidesPerScroll | number | 1 | Slides to scroll per nav click on desktop | | slideGap | number | 0 | Gap between slides in pixels |

Note: Desktop breakpoint is min-width: 64rem (1024px)

Tip: Use 'auto' for slidesPerView when slides have different/natural widths defined in CSS.

Behavior

| Option | Type | Default | Description | |--------|------|---------|-------------| | direction | 'horizontal' \| 'vertical' | 'horizontal' | Slider orientation | | enableDragToScroll | boolean | false | Enable mouse/touch drag to scroll | | loop | boolean | false | Enable infinite loop navigation | | autoplay | boolean | false | Enable automatic slide advancement | | autoplayInterval | number | 3000 | Autoplay interval in milliseconds | | pauseOnHover | boolean | true | Pause autoplay on hover/touch | | autoScroll | boolean | false | Enable auto-scroll (continuous scrolling, implicitly enables loop) | | autoScrollSpeed | number | 50 | Auto-scroll speed in pixels per second | | autoScrollDirection | 'forward' \| 'backward' | 'forward' | Auto-scroll direction | | autoScrollStartDelay | number | 0 | Delay in ms before auto-scroll starts or resumes after interaction | | stopOnInteraction | boolean | false | Permanently stop auto-scroll after user drag (call play() to restart) | | stopOnMouseEnter | boolean | true | Pause auto-scroll when mouse hovers over the slider | | stopOnFocusIn | boolean | true | Pause auto-scroll when a slide receives keyboard focus | | easing | EasingFunction | easeOutExpo | Custom easing function |

Note: When autoScroll is enabled, loop mode is automatically enabled. Auto-scroll integrates with drag-to-scroll: dragging pauses the scroll, and it resumes after the drag ends (unless stopOnInteraction is true).

Scrollbar (Optional)

| Option | Type | Default | Description | |--------|------|---------|-------------| | scrollbarThumb | HTMLElement \| null | null | Custom scrollbar thumb element | | scrollbarTrack | HTMLElement \| null | null | Custom scrollbar track element |

Callbacks

| Option | Type | Description | |--------|------|-------------| | onScrollStart | (params) => void | Fired when scroll animation starts | | onScroll | (params) => void | Fired during scroll | | onScrollEnd | (params) => void | Fired when scroll animation ends |

Callback Parameters

// onScrollStart
{
  currentScroll: number    // Current scroll position in pixels
  target: HTMLElement      // Target slide element
  direction: 'prev' | 'next'
}

// onScroll, onScrollEnd
{
  currentScroll: number      // Current scroll position in pixels
  currentSlideIndex: number  // Index of the current slide
}

API Methods

const slider = createSlider({ /* options */ })

// Navigate to specific slide (0-indexed)
slider.goToIndex(2)

// Navigate prev/next (respects loop setting)
slider.next()
slider.prev()

// Autoplay/Auto-scroll control
slider.play()   // Start autoplay or auto-scroll
slider.pause()  // Stop autoplay or auto-scroll

// Recalculate dimensions (call after DOM changes)
slider.refresh()

// Cleanup all event listeners
slider.unload()

Easing Functions

Built-in easing functions for smooth animations:

import {
  createSlider,
  easeOutExpo,    // Default - smooth deceleration
  easeOutCubic,   // Gentler deceleration
  easeInOutCubic, // Smooth acceleration & deceleration
  easeOutQuad,    // Gentle deceleration
  linear          // Constant speed
} from 'lazer-slider'

const slider = createSlider({
  // ... options
  easing: easeOutCubic
})

Custom Easing

// Custom easing function (t goes from 0 to 1)
const customEase = (t: number) => t * t * t

const slider = createSlider({
  // ... options
  easing: customEase
})

Examples

Basic Slider

const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')]
})

With Navigation Buttons

const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  prevSlideButton: document.querySelector('.prev'),
  nextSlideButton: document.querySelector('.next')
})

Responsive Multi-Slide

const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  mobileSlidesPerView: 1,
  desktopSlidesPerView: 3,
  slideGap: 16,
  mobileSlidesPerScroll: 1,
  desktopSlidesPerScroll: 3
})

Autoplay Carousel

const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  loop: true,
  autoplay: true,
  autoplayInterval: 5000,
  pauseOnHover: true
})

Auto-Scroll

Create a continuous scrolling effect with seamless infinite loop. Perfect for ticker-style content, logos, or announcements. Integrates with drag-to-scroll for smooth user interaction.

const ticker = createSlider({
  feed: document.querySelector('.ticker-feed'),
  slides: [...document.querySelectorAll('.ticker-item')],
  autoScroll: true,
  autoScrollSpeed: 80,              // 80 pixels per second
  autoScrollDirection: 'forward',   // or 'backward'
  enableDragToScroll: true,         // Users can drag to interact
  autoScrollStartDelay: 1000,       // Resume 1s after interaction
  stopOnMouseEnter: true            // Pause on hover (default)
})

// Control programmatically
ticker.pause()  // Stop auto-scroll
ticker.play()   // Resume auto-scroll

Key Features:

  • Continuous smooth scrolling using requestAnimationFrame (not CSS animations)
  • Frame-rate independent animation for consistent speed across devices
  • Automatic loop clone management for seamless infinite scrolling
  • Drag interaction support: dragging pauses auto-scroll, resumes after release
  • Pause on hover, touch, and keyboard focus
  • Configurable resume delay after user interaction
  • stopOnInteraction mode for one-time auto-scroll that stops after user engagement

When to use Auto-Scroll vs Autoplay:

  • Auto-Scroll: For continuous ticker-style scrolling (news tickers, logo carousels, announcements)
  • Autoplay: For slide-by-slide carousel navigation (image galleries, product showcases)

Vertical Slider

Create a vertical scrolling slider for content that flows top-to-bottom.

<div class="vertical-slider">
  <button class="slider-prev">↑</button>
  <button class="slider-next">↓</button>

  <div class="slider-feed">
    <div class="slide">Slide 1</div>
    <div class="slide">Slide 2</div>
    <div class="slide">Slide 3</div>
  </div>
</div>
.slider-feed {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  height: 400px; /* Set a fixed height for vertical scrolling */
  scrollbar-width: none;
}

.slider-feed::-webkit-scrollbar {
  display: none;
}

.slide {
  scroll-snap-align: start;
  flex-shrink: 0;
}
const verticalSlider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  prevSlideButton: document.querySelector('.slider-prev'),
  nextSlideButton: document.querySelector('.slider-next'),
  direction: 'vertical',
  mobileSlidesPerView: 1,
  desktopSlidesPerView: 1,
  enableDragToScroll: true
})

Key Features:

  • Full vertical scrolling support with smooth animations
  • Keyboard navigation with ArrowUp/ArrowDown keys
  • Drag-to-scroll works in vertical direction
  • Loop mode and autoplay work vertically
  • Custom scrollbar can be styled vertically

When to use Vertical Slider:

  • Full-page vertical carousels
  • Testimonial/review sliders with tall content
  • Timeline or step-by-step displays
  • Mobile-first vertical content experiences

Bullets/Dots Navigation

You can add clickable dots that navigate to specific slides using either manual or automatic generation.

Automatic Bullets (Recommended)

The slider can automatically generate bullets from your slides. Just provide a container element:

<div class="slider">
  <div class="slider-feed">
    <div class="slide">Slide 1</div>
    <div class="slide">Slide 2</div>
    <div class="slide">Slide 3</div>
  </div>

  <div class="slider-bullets"></div>
</div>
.slider-bullets {
  display: flex;
  gap: 8px;
  justify-content: center;
  margin-top: 16px;
}

.slider-bullet {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: none;
  background: #ccc;
  cursor: pointer;
  transition: background 0.3s;
}

.slider-bullet.active {
  background: #333;
}
const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  bulletsContainer: document.querySelector('.slider-bullets'),
  bulletsClass: 'slider-bullet',      // Optional: default is 'slider-bullet'
  bulletsActiveClass: 'active'        // Optional: default is 'active'
})

Features:

  • Automatically generates one bullet per visible slide
  • Full ARIA support (role="tablist", aria-selected, etc.)
  • Active state automatically managed
  • Only generates bullets for visible slides (hidden slides are skipped)

Manual Bullets

Alternatively, you can create bullets manually and pass them via the thumbs option:

<div class="slider">
  <div class="slider-feed">
    <div class="slide">Slide 1</div>
    <div class="slide">Slide 2</div>
    <div class="slide">Slide 3</div>
  </div>

  <div class="slider-dots">
    <button class="dot" aria-label="Go to slide 1"></button>
    <button class="dot" aria-label="Go to slide 2"></button>
    <button class="dot" aria-label="Go to slide 3"></button>
  </div>
</div>
.slider-dots {
  display: flex;
  gap: 8px;
  justify-content: center;
  margin-top: 16px;
}

.dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: none;
  background: #ccc;
  cursor: pointer;
  transition: background 0.3s;
}

.dot.active {
  background: #333;
}
const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  thumbs: [...document.querySelectorAll('.dot')]
})

Note: If both bulletsContainer and thumbs are provided, bulletsContainer is ignored and manual thumbs are used instead.

Thumbs Gallery

Auto-generate clickable thumbnail images from your slides. Perfect for product galleries, image carousels, and any slider where visual previews enhance navigation.

<div class="slider">
  <div class="slider-feed">
    <div class="slide">
      <img src="/images/product-1.jpg" alt="Product 1">
      <h3>Product Title 1</h3>
    </div>
    <div class="slide">
      <img src="/images/product-2.jpg" alt="Product 2">
      <h3>Product Title 2</h3>
    </div>
    <div class="slide">
      <img src="/images/product-3.jpg" alt="Product 3">
      <h3>Product Title 3</h3>
    </div>
  </div>

  <!-- Empty container - thumbnails are auto-generated -->
  <div class="thumbs-gallery"></div>
</div>
.thumbs-gallery {
  display: flex;
  gap: 8px;
  margin-top: 16px;
  justify-content: center;
}

.slider-thumb {
  padding: 0;
  border: 2px solid transparent;
  background: none;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.2s, border-color 0.2s;
}

.slider-thumb:hover {
  opacity: 0.8;
}

.slider-thumb.active {
  opacity: 1;
  border-color: #007bff;
}

.slider-thumb img {
  display: block;
  border-radius: 4px;
}
const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  thumbsContainer: document.querySelector('.thumbs-gallery'),
  thumbsClass: 'slider-thumb',           // Optional: default is 'slider-thumb'
  thumbsActiveClass: 'active',           // Optional: default is 'active'
  thumbImageSelector: 'img',             // Optional: CSS selector for slide images
  thumbSize: { width: 80, height: 60 }   // Optional: fixed thumbnail dimensions
})

Features:

  • Automatically extracts images from each slide using thumbImageSelector
  • Creates clickable thumbnail buttons with proper accessibility attributes
  • Full ARIA support (role="tablist", aria-selected, etc.)
  • Active state automatically managed on scroll and click
  • Only generates thumbnails for visible slides
  • Optional fixed dimensions with thumbSize for consistent layouts

When to use Thumbs Gallery vs Bullets:

  • Thumbs Gallery: For image-heavy sliders where visual previews help navigation (product galleries, portfolios)
  • Bullets: For simpler navigation where slide content preview isn't needed (text carousels, testimonials)

Custom Scrollbar

Create a draggable scrollbar that syncs with the slider position.

<div class="slider">
  <div class="slider-feed">
    <div class="slide">Slide 1</div>
    <div class="slide">Slide 2</div>
    <div class="slide">Slide 3</div>
  </div>

  <div class="scrollbar-track">
    <div class="scrollbar-thumb"></div>
  </div>
</div>
.scrollbar-track {
  width: 100%;
  height: 4px;
  background: #eee;
  border-radius: 2px;
  margin-top: 16px;
  position: relative;
}

.scrollbar-thumb {
  height: 100%;
  background: #333;
  border-radius: 2px;
  /* Width is set automatically by the slider */
}
const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  scrollbarThumb: document.querySelector('.scrollbar-thumb'),
  scrollbarTrack: document.querySelector('.scrollbar-track')
})

The slider automatically:

  • Sets the thumb width based on visible content ratio
  • Updates thumb position on scroll
  • Hides the scrollbar when all content is visible

Auto Width Slides

Use slidesPerView: 'auto' when slides have different natural widths defined in CSS.

<div class="slider-feed">
  <div class="slide slide--small">Small</div>
  <div class="slide slide--medium">Medium</div>
  <div class="slide slide--large">Large content here</div>
</div>
.slide--small { width: 150px; }
.slide--medium { width: 250px; }
.slide--large { width: 400px; }
const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  mobileSlidesPerView: 'auto',
  desktopSlidesPerView: 'auto',
  slideGap: 16,
  enableDragToScroll: true
})

Full Featured

const slider = createSlider({
  feed: document.querySelector('.slider-feed'),
  slides: [...document.querySelectorAll('.slide')],
  prevSlideButton: document.querySelector('.prev'),
  nextSlideButton: document.querySelector('.next'),

  // Navigation (choose one: bullets OR thumbs)
  bulletsContainer: document.querySelector('.slider-bullets'), // Auto-generated bullets
  // OR for image galleries:
  // thumbsContainer: document.querySelector('.thumbs-gallery'),
  // thumbSize: { width: 80, height: 60 },

  // Responsive
  mobileSlidesPerView: 1,
  desktopSlidesPerView: 3,
  slideGap: 20,

  // Behavior
  enableDragToScroll: true,
  loop: true,
  autoplay: true,
  autoplayInterval: 4000,

  // Callbacks
  onScrollEnd: ({ currentSlideIndex }) => {
    console.log('Now viewing slide:', currentSlideIndex)
  }
})

Accessibility

The slider automatically adds ARIA attributes for accessibility:

  • role="region" and aria-roledescription="carousel" on the feed
  • role="group" and aria-label="Slide X of Y" on each slide
  • aria-controls linking buttons to the feed
  • aria-hidden and tabindex management for visibility
  • Keyboard navigation with arrow keys when feed is focused

TypeScript

Full TypeScript support with exported types:

import {
  createSlider,
  generateBullets,
  generateThumbs,
  type SliderSettings,
  type Slider,
  type EasingFunction,
  type ScrollParams,
  type ScrollStartParams,
  type SliderDirection,
  type AutoScrollDirection
} from 'lazer-slider'

Note: generateBullets and generateThumbs are also exported if you need to manually generate navigation elements outside of the slider initialization.

Styling

Optional CSS

Lazer Slider ships with an optional stylesheet that provides ready-to-use styles:

import 'lazer-slider/css'

CSS Classes

The stylesheet provides these class names:

| Class | Description | |-------|-------------| | .lazer-slider | Optional wrapper container | | .lazer-feed | Scrollable container (horizontal) | | .lazer-feed.lazer-vertical | Vertical slider variant | | .lazer-feed.lazer-draggable | Adds grab cursor for drag-enabled sliders | | .lazer-slide | Individual slide | | .lazer-bullets | Bullets container | | .lazer-bullet | Individual bullet/dot | | .lazer-thumbs | Thumbnails container | | .lazer-thumb | Individual thumbnail | | .lazer-track | Scrollbar track | | .lazer-scrollbar | Scrollbar thumb | | .lazer-nav | Navigation button base | | .lazer-prev | Previous button | | .lazer-next | Next button |

CSS Custom Properties

Customize the look by overriding CSS variables:

:root {
  /* Bullets */
  --lazer-bullet-size: 12px;
  --lazer-bullet-gap: 8px;
  --lazer-bullet-color: #cbd5e1;
  --lazer-bullet-active-color: #1e293b;
  --lazer-bullet-hover-scale: 1.2;

  /* Thumbnails */
  --lazer-thumb-gap: 8px;
  --lazer-thumb-opacity: 0.6;
  --lazer-thumb-hover-opacity: 0.8;
  --lazer-thumb-active-opacity: 1;
  --lazer-thumb-border-width: 2px;
  --lazer-thumb-border-color: transparent;
  --lazer-thumb-active-border-color: #3b82f6;
  --lazer-thumb-border-radius: 4px;

  /* Scrollbar */
  --lazer-track-height: 4px;
  --lazer-track-color: #e2e8f0;
  --lazer-thumb-color: #1e293b;
  --lazer-track-radius: 2px;

  /* Navigation */
  --lazer-nav-size: 40px;
  --lazer-nav-offset: 12px;
  --lazer-nav-bg: rgba(255, 255, 255, 0.9);
  --lazer-nav-hover-bg: white;
  --lazer-nav-radius: 50%;
  --lazer-nav-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  /* Transitions */
  --lazer-duration: 0.3s;
  --lazer-easing: ease;
}

Custom Styles (No CSS Import)

If you prefer not to use the included styles, here's the minimal CSS required:

.slider-feed {
  display: flex;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.slider-feed::-webkit-scrollbar {
  display: none;
}

.slide {
  flex-shrink: 0;
}

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Requires scroll-snap-type CSS support for snap behavior.

License

UNLICENSED