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
The post Native Scroll Carousel Web Component with Drag – Blossom Carousel appeared first on CSS Script.
Discover more from RSS Feeds Cloud
Subscribe to get the latest posts sent to your email.
