
Slickless is a dependency-free JavaScript carousel library that creates responsive, touch-enabled, highly customizable carousel sliders.
It works as a modern jQuery slick.js plugin alternative for developers who want familiar carousel options, touch-friendly dragging, pagination dots, navigation arrows, autoplay, lazy images, responsive breakpoints, and synced gallery navigation.
Features:
- Create carousels from existing HTML elements.
- Arrows and pagination dots.
- Touch, mouse, and pen dragging.
- Loop slides with cloned edge items.
- Autoplay in both directions.
- Slide, fade, vertical, and centered layouts.
- Adjust layout at responsive breakpoints.
- Load carousel images on demand.
- Respect reduced-motion preferences.
- Keyboard navigation and carousel roles.
- Sync two carousels for gallery navigation.
- Update, insert, remove, and rebuild slides.
Use Cases:
- Product image galleries that need accessible keyboard navigation on e-commerce detail pages.
- Testimonial sliders on landing pages where the project already excludes jQuery from the bundle.
- Hero carousels that synchronize a main image view with a thumbnail navigation strip below it.
- Content tours or onboarding slides that auto-advance and skip animation for users with reduced-motion preferences enabled.
How to use it:
1. Install Slickless with NPM and import it into your JavaScript entry point:
# NPM $ npm install slickless # PNPM $ pnpm install slickless # BUN $ bun add slickless
import { Slickless } from "slickless";
import "slickless/style.css";2. To use Slickless directly in a browser without a bundler, load it from a CDN in a type="module" script:
<script type="module">
import { Slickless } from "https://esm.sh/[email protected]";
</script>3. Create the HTML for your first carousel. Each direct child of the root element becomes one slide. Add your cards, images, figures, or custom HTML inside the carousel container.
<div id="featured-products">
<article>
<img src="products/desk-lamp.jpg" alt="Adjustable desk lamp" />
<h3>Desk Lamp</h3>
</article>
<article>
<img src="products/wireless-keyboard.jpg" alt="Compact wireless keyboard" />
<h3>Wireless Keyboard</h3>
</article>
<article>
<img src="products/monitor-stand.jpg" alt="Wood monitor stand" />
<h3>Monitor Stand</h3>
</article>
<article>
<img src="products/usb-hub.jpg" alt="USB-C hub" />
<h3>USB-C Hub</h3>
</article>
...
</div>4. Create a three-column carousel with arrows and dot pagination.
const productCarousel = new Slickless("#featured-products", {
slidesToShow: 3,
slidesToScroll: 1,
infinite: true,
arrows: true,
dots: true,
});5. All configuration options.
arrows(boolean): Shows previous and next arrow controls.dots(boolean): Renders dot pagination controls.infinite(boolean): Loops the carousel through cloned slides at the start and end.autoplay(boolean): Moves slides automatically.autoplaySpeed(number): Sets the autoplay interval in milliseconds.autoplayDirection(“forward” | “backward”): Sets the autoplay direction.speed(number): Sets the transition duration in milliseconds.cssEase(string): Sets the CSS easing function for slide transitions.slidesToShow(number): Sets how many slides appear in the viewport.slidesToScroll(number): Sets how many slides move per navigation action.fade(boolean): Uses cross-fade transitions instead of sliding movement.vertical(boolean): Stacks slides vertically and moves them on the Y axis.centerMode(boolean): Centers the active slide and shows nearby slide edges.centerPadding(string): Sets the side padding around the focused slide in center mode.variableWidth(boolean): Uses each slide’s natural width.initialSlide(number): Sets the starting slide index.rtl(boolean): Enables right-to-left direction.draggable(boolean): Enables pointer and touch dragging.swipeThreshold(number): Sets the drag distance required for a swipe.pauseOnHover(boolean): Pauses autoplay when the pointer enters the carousel.pauseOnFocus(boolean): Pauses autoplay when focus moves inside the carousel.accessibility(boolean): Enables keyboard navigation with arrow keys, Home, and End.adaptiveHeight(boolean): Resizes the viewport to match the current slide height.lazyLoad(“ondemand” | “progressive” | false): Loads images fromimg[data-lazy].respectReducedMotion(boolean): Sets transition speed to zero for users who prefer reduced motion.focusOnSelect(boolean): Moves to a slide when the user clicks it.asNavFor(string | HTMLElement | null): Links the carousel to another Slickless instance.prevArrow(string | HTMLElement | null): Provides custom previous arrow markup or an element.nextArrow(string | HTMLElement | null): Provides custom next arrow markup or an element.customPaging((i, total) => HTMLElement | string): Provides custom markup for each pagination dot.responsive(ResponsiveBreakpoint[] | null): Applies breakpoint-specific option overrides. A breakpoint can also use"unslick"to destroy the carousel.ariaRoleDescription(string): Sets the root element’saria-roledescriptionvalue.
6. API methods.
// Move forward by the configured slide step.
productCarousel.next();
// Move backward by the configured slide step.
productCarousel.prev();
// Jump to a specific real slide index. The second argument skips animation.
productCarousel.goTo(2, true);
// Start autoplay for the current carousel instance.
productCarousel.play();
// Stop autoplay for the current carousel instance.
productCarousel.pause();
// Update options. The second argument controls whether Slickless rebuilds the carousel.
productCarousel.setOptions(
{
slidesToShow: 2,
dots: false,
},
true
);
// Insert a new slide at a specific real index.
const promoSlide = document.createElement("article");
promoSlide.innerHTML = "<h3>Limited Deal</h3><p>Save on office accessories.</p>";
productCarousel.addSlide(promoSlide, 1);
// Remove a slide by its real index.
productCarousel.removeSlide(3);
// Get the current real slide index.
const activeIndex = productCarousel.getCurrentSlide();
// Get the number of real slides.
const totalSlides = productCarousel.getSlideCount();
// Get real slide elements only. Cloned loop slides are excluded.
const realSlides = productCarousel.getSlides();
// Subscribe to an instance event. The return value removes the listener.
const removeAfterChange = productCarousel.on("afterChange", function (detail) {
console.log("Current slide:", detail.currentSlide);
});
// Remove an event listener manually.
function handleSwipe(detail) {
console.log("Swipe direction:", detail.direction);
}
productCarousel.on("swipe", handleSwipe);
productCarousel.off("swipe", handleSwipe);
// Recalculate layout after a container or slide size change.
productCarousel.refresh();
// Restore the original DOM and remove Slickless listeners.
productCarousel.destroy();
// Rebuild the carousel with the current options.
productCarousel.reInit();7. Events.
// Fires after the first carousel build.
productCarousel.on("init", function (detail) {
console.log(detail.slickless);
});
// Fires before a slide transition starts.
productCarousel.on("beforeChange", function (detail) {
console.log(detail.currentSlide, detail.nextSlide);
});
// Fires after a slide transition finishes.
productCarousel.on("afterChange", function (detail) {
console.log(detail.currentSlide);
});
// Fires after the user completes a swipe gesture.
productCarousel.on("swipe", function (detail) {
console.log(detail.direction);
});
// Fires when a non-looping carousel reaches the start or end.
productCarousel.on("edge", function (detail) {
console.log(detail.direction);
});
// Fires when a responsive breakpoint changes.
productCarousel.on("breakpoint", function (detail) {
console.log(detail.breakpoint);
});
// Fires when a lazy image loads.
productCarousel.on("lazyLoaded", function (detail) {
console.log(detail.image, detail.src);
});
// Fires when a lazy image fails to load.
productCarousel.on("lazyLoadError", function (detail) {
console.log(detail.image, detail.src);
});
// Fires after the carousel restores the original DOM.
productCarousel.on("destroy", function (detail) {
console.log(detail.slickless);
});
// Fires after the carousel rebuilds.
productCarousel.on("reInit", function (detail) {
console.log(detail.slickless);
});
// DOM CustomEvent format for browser event listeners.
document.querySelector("#featured-products").addEventListener("slickless:afterChange", function (event) {
console.log(event.detail.currentSlide);
});Advanced Examples:
Autoplay with pause on hover and focus
Marketing hero carousels often run automatically but should stop when a visitor focuses or hovers to read the content. Slickless handles both pause behaviors through two separate options, and both default to true when autoplay is on.
const heroSlider = new Slickless("#hero-carousel", {
autoplay: true,
autoplaySpeed: 5000, // Advance every 5 seconds
autoplayDirection: "forward",
pauseOnHover: true, // Pauses when the cursor is over the carousel
pauseOnFocus: true, // Also pauses when focus enters any slide
dots: true,
infinite: true,
});
Multi-slide view with responsive breakpoints
Logo rows and product grids typically need three or four slides on desktop and one slide on mobile. The responsive array overrides specific options at each pixel-width breakpoint, and breakpoints are checked against window.innerWidth.
const logoRow = new Slickless("#partner-logos", {
slidesToShow: 4,
slidesToScroll: 2,
infinite: true,
arrows: true,
responsive: [
{
breakpoint: 1024,
settings: {
slidesToShow: 3,
slidesToScroll: 1,
},
},
{
breakpoint: 640,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
},
],
});
Synchronized thumbnail navigation
Product lookbooks and image viewers often pair a main carousel with a thumbnail strip below it. The asNavFor option links both instances bidirectionally so clicking either one advances the other.
// Main image viewer — links to the thumbnail strip
const mainView = new Slickless("#main-viewer", {
slidesToShow: 1,
infinite: false,
asNavFor: "#thumb-nav",
});
// Thumbnail strip — initialize before passing its selector to the main carousel
const thumbNav = new Slickless("#thumb-nav", {
slidesToShow: 4,
slidesToScroll: 1,
focusOnSelect: true, // Clicking a thumbnail jumps the main viewer to that slide
infinite: false,
asNavFor: "#main-viewer",
});
Lazy loading images
Pages with many high-resolution slides benefit from deferring loads until slides near the viewport need to render. Set the image src as a data-lazy attribute and pick a load strategy in the options.
<div id="feature-slider"> <div><img data-lazy="photo-01.jpg" alt="Feature 1" /></div> <div><img data-lazy="photo-02.jpg" alt="Feature 2" /></div> <div><img data-lazy="photo-03.jpg" alt="Feature 3" /></div> </div>
const featureSlider = new Slickless("#feature-slider", {
lazyLoad: "ondemand", // Loads images for the current and adjacent slides only
slidesToShow: 1,
infinite: true,
});
Alternatives:
- More Vanilla JavaScript & CSS Carousels
- 10 Best JavaScript Carousel Libraries
- Touch-Ready, High-Performance Vanilla JS Slider – Pagiflow
- Mobile-Friendly Carousel Slider with Responsive Breakpoints – SuperSlider
- Fast, Extensible, Touch-enabled Carousel & Slider JS Library – Smooothy
- Responsive Adaptive Slider/Carousel with Vanilla JS – No jQ Slider
- Swipeable Image Carousel In Vanilla JavaScript – otslider
FAQs:
Q: Is Slickless a drop-in replacement for slick.js?
A: No. Slickless keeps many familiar option names, but it uses new Slickless(el, options) and different DOM class names.
Q: Why does my carousel appear unstyled?
A: The Slickless stylesheet is probably missing. Import slickless/style.css in your JavaScript entry file or include the CSS from your build pipeline.
Q: Does Slickless work with TypeScript?
A: Yes. The package includes type declarations, so TypeScript projects can read the constructor, options, methods, and event details.
Q: How should I handle slides loaded from an API?
A: Create slide elements after the API response arrives, then use addSlide() for inserts or call reInit() after larger DOM changes. Initialize the carousel after the first slide set exists when possible.
Q: The carousel layout looks broken or zero-width when it initializes. What causes that?
A: The container is likely hidden or has zero width at initialization time. The layout calculations use getBoundingClientRect(), which returns zero for elements with no visible box. Initialize after the container is visible, or call reInit() once it appears.







