Features
- Flexible item sizing with automatic recalculations across breakpoints
- Simple setup: navigation buttons and keyboard controls work with minimal markup
- Allows for multiple carousels per page without initialization conflicts
- Native CSS gives performant, GPU-accelerated scrolling
- Javascript API and events available for complex builds
Setup
How It Works
I started coding a carousel library on top of SwiperJS for days until I remembered how difficult it always is to work with – especially when it came to controlling the styling. So I threw that library in the trash.
This new library was made to be easy to work with (especially on Webflow) without fighting unexpected Javascript shenanigans. You style things like always and control item sizes and spacing with standard CSS properties.
In the background, ResizeObserver detects changes and automatically recalculates positions and boundaries. CSS Scroll Snap handles alignment. scrollIntoView() provides the smooth programmatic navigation.
The benefit of leveraging native browser features instead of a library like SwiperJS is that the carousel gets GPU acceleration and performance optimizations for free. And it's just smoother. Less JavaScript, better performance, smoother scrolling.
Structure
There's three elements required:
[data-carousel="container"]– the outermost container, used for scoping a specific instance.[data-carousel="track"]– the horizontal list that houses all the items.[data-carousel="item"]– the individual slides.
The purpose of the [data-carousel="container"] element is three-fold:
- It allows for automatic unique instance detection. Translation: you can simply have elements like the previous/next navigation buttons exist within the
[data-carousel="container"], and the code already knows those buttons are for that specific carousel. It won't randomly start moving another carousel on another part of the page. - It works perfectly with Webflow CMS. How do you get navigation buttons inside Webflow's Collection List if you can't put non-CMS things inside a Collection List? Exactly. Now you can.
- Flexibility for future features. Wink.
You can put whatever you want inside of the [data-carousel="container"] element. The [data-carousel="track"] can be nested arbitrarily deep.
The only restraints: there can only be one carousel within a container, and all the [data-carousel="item"] elements must be direct children of the track.
Webflow CMS
To match up with the Webflow Collection List structure:
[data-carousel="container"]– goes on theCollection List Wrapper(if you don't care for navigation buttons), or anywhere as an ancestor of theCollection List Wrapper.[data-carousel="track"]– goes on theCollection List– goes on the (you guessed it!)
<div data-carousel="container">
<div data-carousel="track">
<div data-carousel="item">Item 1</div>
<div data-carousel="item">Item 2
Customization
Core Attributes
Configure the carousel with data attributes on the container element:
| Attribute | Values | Default | Description |
|---|---|---|---|
data-carousel | "container" | - | Required on container element |
data-carousel | "track" | - | Required on track element |
data-carousel | "item" | - | Required on each item element |
|
Navigation Elements
Optional navigation controls that work automatically when placed inside the container:
| Attribute | Description |
|---|---|
data-carousel="prev" | Previous button |
data-carousel="next" | Next button |
Pagination is coming very soon. Doing some bug testing.
Keyboard Navigation
When enabled, Arrow Left and Arrow Right navigate between items. For those with huge keyboards, Home jumps to first item and End jumps to the last item.
To enable keyboard navigation:
<div data-carousel="container" data-carousel-keyboard="true">
<!-- Rest of the track/items -->
</div>Spacing and Positioning
Use CSS gap on the [data-carousel="track"] to control spacing.
The library also works with CSS scroll-padding (container insets) and scroll-margin (per-item offsets) for controlling snap positioning. For the variants on this page, all of them have scroll-padding on the track element.
State Classes
The library applies state classes that you can style however you want.
| Class | Applied to |
|---|---|
.carousel-item-active | The item that is currently active (i.e. aligned) |
.carousel-button-disabled | Navigation buttons at start/end boundaries |
.carousel-scrolling | The track while a user or programmatic scrolling is active |
.carousel-snap-disabled | The track to temporarily disable scroll-snap during button navigation |
.carousel-animating | The track during programmatic scroll animations |
|
Here's an example from the prev/next buttons on all the variants on this page:
/* Arrow initial styles */
.carousel_arrow {
cursor: pointer;
transition:
opacity 200ms ease-in-out,
color 150ms ease-in-out;
}
/* For a disabled arrow */
JavaScript API
Manual Initialization
// Auto-initializes on page load by default
// Or manually initialize:
const carousel = new Carousel(
document.querySelector('[data-carousel="container"]'),
);
// Or using selector:
const carousel =Instance Methods
carousel.next(); // Go to next item
carousel.prev(); // Go to previous item
carousel.goTo(2); // Go to specific index (0-based)
carousel.getActiveIndex
All methods are chainable:
carousel.next().next().refresh();Events
carousel.on("change", (e) => {
console.log(`Active item: ${e.index}`);
})
Or use native DOM events:
container.addEventListener("carousel:change", (e) => {
console.log(e.detail); // { carousel, index }
});Available events:
| Event | Description | Event Data |
|---|---|---|
change | Active item changed | { index } |
scroll | Track scrolled | { scrollLeft } |
reach-start | Scrolled to first item | - |
reach-end | Scrolled to last item | - |
Global Registry
Access all carousel instances:
// Get all instances
const instances = window.CarouselInstances;
// Get specific instance by ID
const carousel = instances.get("carousel-1");
// Iterate over all
instances.forEach
Dynamic Content
When adding/removing items, call refresh():
const track = carousel.track;
const newItem = document.createElement("div");
newItem.setAttribute("data-carousel", "item");
newItem.
For major structural changes, destroy and reinitialize:
carousel.destroy();
const newCarousel = new Carousel(container);