
Blossom Carousel is a dependency-free JavaScript carousel library that adds mouse-drag support to a horizontally scrolling container and keeps the browser’s own scroll behavior in place.
Native scroll containers already handle touch swiping, keyboard scrolling, and screen reader compatibility well.
Blossom Carousel builds on that and adds a physics-based drag functionality for pointer devices, the one interaction native scrolling does not provide on desktop.
Features:
- Native horizontal scrolling preserves normal browser scroll behavior and DOM state.
- Physics-based dragging adds mouse and fine-pointer interaction.
- CSS controls slide width, gaps, breakpoints, scroll snap, sticky elements, and visual effects.
- Previous and next controls follow configured snap positions.
- Dot navigation tracks elements marked with
data-blossom-slide. - Custom dot templates support numbered controls, icons, and thumbnails.
- Web Component, React, Vue, Svelte, and framework-agnostic Core packages.
- Right-to-left layouts follow the carousel’s CSS direction.
- A cancelable
overscrollevent supports custom edge effects. - Core instances expose initialization, navigation, and cleanup methods.
- Conditional Core imports can limit the drag enhancement to fine-pointer devices.
- Experimental repeat mode creates a cyclical scrolling effect.
How To Use It:
Installation
Choose the package that matches your app architecture.
| Environment | Package |
|---|---|
| Framework-free Web Component | @blossom-carousel/web |
| Direct JavaScript Core API | @blossom-carousel/core |
| React and Next.js | @blossom-carousel/react |
| Vue and Nuxt | @blossom-carousel/vue |
| Svelte and SvelteKit | @blossom-carousel/svelte |
The Web Component package supports both NPM projects and static pages.
npm install @blossom-carousel/web
Import the component registration and its base stylesheet from the application entry point.
import "@blossom-carousel/web"; import "@blossom-carousel/web/style.css";
Static pages can load the UMD build from a CDN.
<link rel="stylesheet" href="https://unpkg.com/@blossom-carousel/web/dist/blossom-carousel-web.css" /> <script defer src="https://unpkg.com/@blossom-carousel/web/dist/blossom-carousel-web.umd.js" ></script>
Basic Usage
The carousel needs a horizontal layout and overflowing content. Add data-blossom-slide to each tracked slide when the interface uses dot navigation.
The example below creates a responsive product rail with scroll snapping, previous and next controls, and automatic dot markers.
<section
class="featured-products"
aria-labelledby="featured-products-title"
>
<h2 id="featured-products-title">Featured products</h2>
<blossom-carousel
id="featured-products"
class="product-carousel"
>
<article class="product-card" data-blossom-slide>
<img src="product-1.jpg" alt="Canvas travel bag" />
<h3>Canvas Travel Bag</h3>
<a href="/products/canvas-travel-bag">View product</a>
</article>
<article class="product-card" data-blossom-slide>
<img src="product-2.jpg" alt="Ceramic desk lamp" />
<h3>Ceramic Desk Lamp</h3>
<a href="/products/ceramic-desk-lamp">View product</a>
</article>
<article class="product-card" data-blossom-slide>
<img src="product-3.jpg" alt="Oak storage tray" />
<h3>Oak Storage Tray</h3>
<a href="/products/oak-storage-tray">View product</a>
</article>
</blossom-carousel>
<div class="carousel-controls">
<blossom-prev
for="featured-products"
aria-label="Previous products"
>
Previous
</blossom-prev>
<blossom-dots for="featured-products"></blossom-dots>
<blossom-next
for="featured-products"
aria-label="Next products"
>
Next
</blossom-next>
</div>
</section>
.product-carousel {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(16rem, 75%);
gap: 1rem;
overflow-x: auto;
padding: 0.5rem 1rem 1rem;
scroll-padding-inline: 1rem;
scroll-snap-type: inline mandatory;
}
.product-card {
scroll-snap-align: start;
border: 1px solid #d7d7d7;
border-radius: 0.75rem;
padding: 1rem;
}
.product-card img {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 0.5rem;
}
.carousel-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-top: 1rem;
}
@media (min-width: 48rem) {
.product-carousel {
grid-auto-columns: calc((100% - 2rem) / 3);
}
}
Navigation Controls
<blossom-prev>, <blossom-next>, and <blossom-dots> can be placed outside the scrolling element. Their for attribute must match the carousel’s id.
Previous and next controls search for the next valid snap point. A carousel without scroll snap moves by a proportional distance. The controls also account for scroll padding and right-to-left direction.
Dot navigation counts elements marked with data-blossom-slide. The current dot receives aria-current, while previous and next buttons become disabled at the start and end of a bounded carousel.
<blossom-carousel id="article-list" class="carousel"> <article data-blossom-slide>Article 1</article> <article data-blossom-slide>Article 2</article> <article data-blossom-slide>Article 3</article> </blossom-carousel> <blossom-prev for="article-list">Previous articles</blossom-prev> <blossom-dots for="article-list"></blossom-dots> <blossom-next for="article-list">Next articles</blossom-next>
Styling The Dot Controls
Default dots use CSS custom properties placed on <blossom-dots> or an ancestor.
--blossom-dot-size: Width and height of each marker.--blossom-dot-radius: Corner radius of each marker.--blossom-dot-color: Marker color.--blossom-dot-opacity: Default marker opacity.--blossom-dot-hover-opacity: Marker opacity on hover.--blossom-dot-active-opacity: Opacity of the current marker.
.carousel-controls {
--blossom-dot-size: 0.75rem;
--blossom-dot-radius: 0.2rem;
--blossom-dot-color: #222;
--blossom-dot-opacity: 0.25;
--blossom-dot-hover-opacity: 0.65;
--blossom-dot-active-opacity: 1;
}
Custom Thumbnail Dots
Set the renderDot property when each navigation marker needs different content. The callback receives the slide index, current state, and target carousel ID.
Return a button for each slide. Blossom adds the navigation command and target relationship.
<blossom-dots
id="product-thumbnails"
for="featured-products"
></blossom-dots>
<script type="module">
const dots = document.getElementById("product-thumbnails");
dots.renderDot = (index, active) => {
const button = document.createElement("button");
button.type = "button";
button.className = "thumbnail-dot";
button.dataset.active = String(active);
button.setAttribute("data-blossom-dot", "");
button.setAttribute(
"aria-label",
`Show product ${index + 1}`
);
button.innerHTML = `
<img
src="/images/product-thumb-${index + 1}.jpg"
alt=""
/>
`;
return button;
};
</script>
Direct Core API
Use @blossom-carousel/core when a custom element or framework component does not fit your project. Pass the scrolling element to Blossom(), then call init().
The returned object provides these public methods:
init(): Adds drag behavior, observers, scroll handling, and snap detection.destroy(): Removes listeners and observers associated with the instance.prev(options): Scrolls to the previous slide or snap position.next(options): Scrolls to the next slide or snap position.
The optional navigation alignment accepts start, center, or end.
npm install @blossom-carousel/core
import { Blossom } from "@blossom-carousel/core";
import "@blossom-carousel/core/style.css";
const track = document.querySelector("#core-carousel");
const previousButton = document.querySelector("[data-carousel-prev]");
const nextButton = document.querySelector("[data-carousel-next]");
if (track instanceof HTMLElement) {
const carousel = Blossom(track);
carousel.init();
previousButton?.addEventListener("click", () => {
carousel.prev({ align: "center" });
});
nextButton?.addEventListener("click", () => {
carousel.next({ align: "center" });
});
window.addEventListener(
"pagehide",
() => {
carousel.destroy();
},
{ once: true }
);
}
Lazy-Loading The Core Package
Touch devices already provide direct swipe scrolling. A conditional import can load the drag engine only when the browser reports a fine pointer and hover support.
const hasFinePointer = window.matchMedia(
"(hover: hover) and (pointer: fine)"
).matches;
if (hasFinePointer) {
const { Blossom } = await import("@blossom-carousel/core");
const track = document.querySelector("#lazy-carousel");
if (track instanceof HTMLElement) {
const carousel = Blossom(track);
carousel.init();
}
}
Command Events
Navigation components dispatch command events to their target carousel.
The command value identifies the requested action:
--blossom-prev--blossom-next--blossom-goto-{index}
Dragging and free scrolling do not dispatch these commands. Use the event when application logic needs to record clicks on arrows or dots.
const carousel = document.getElementById("featured-products");
carousel.addEventListener("command", (event) => {
const command =
event.command ?? event.detail?.command;
console.log("Carousel command:", command);
});
Overscroll Effects
The overscroll event fires when dragging passes a carousel edge. Calling preventDefault() removes the built-in rubber-band transform and leaves the visual response to application code.
The event’s detail.left value represents the horizontal overscroll distance.
const carousel = document.getElementById("featured-products");
carousel.addEventListener("overscroll", (event) => {
event.preventDefault();
const distance = Math.abs(event.detail.left);
const scale = Math.max(0.94, 1 - distance * 0.001);
Array.from(carousel.children).forEach((slide) => {
slide.style.transform = `scale(${scale})`;
});
});
Experimental Repeat Mode
The Web Component accepts a repeat attribute for cyclical scrolling. The Core package uses { repeat: true }.
Repeat remains experimental. Test slide widths, focus order, navigation controls, and dynamic content before using it in production.
<blossom-carousel repeat class="carousel"> <div class="slide">Slide 1</div> <div class="slide">Slide 2</div> <div class="slide">Slide 3</div> <div class="slide">Slide 4</div> </blossom-carousel>
const carousel = Blossom(track, {
repeat: true
});
carousel.init();
Framework Integration
React
Import the stylesheet from a global entry point in Next.js applications.
import {
BlossomCarousel,
BlossomPrev,
BlossomNext,
BlossomDots
} from "@blossom-carousel/react";
export function ProductCarousel({ products }) {
return (
<>
<BlossomCarousel id="products" as="ul">
{products.map((product) => (
<li key={product.id} data-blossom-slide>
{product.name}
</li>
))}
</BlossomCarousel>
<BlossomPrev for="products" />
<BlossomDots for="products" />
<BlossomNext for="products" />
</>
);
}
Vue
<script setup>
import {
BlossomCarousel,
BlossomPrev,
BlossomNext,
BlossomDots
} from "@blossom-carousel/vue";
import "@blossom-carousel/vue/style.css";
</script>
<template>
<BlossomCarousel id="products" as="ul">
<li
v-for="product in products"
-key="product.id"
data-blossom-slide
>
{{ product.name }}
</li>
</BlossomCarousel>
<BlossomPrev for="products" />
<BlossomDots for="products" />
<BlossomNext for="products" />
</template>
Alternatives:
- Draggable & Touch-Friendly Carousel In Vanilla JavaScript
- Touch-Ready, High-Performance Vanilla JS Slider
- Tiny Touch Carousel Web Component for Modern Apps
- High-Performance Slider Carousel JavaScript Library







