https://github.com/matfantinel/carousel-pilot
A zero-dependency, framework-agnostic web component that enhances your existing scrollable content with carousel functionality.
https://github.com/matfantinel/carousel-pilot
carousel css scroll-snap slider
Last synced: 17 days ago
JSON representation
A zero-dependency, framework-agnostic web component that enhances your existing scrollable content with carousel functionality.
- Host: GitHub
- URL: https://github.com/matfantinel/carousel-pilot
- Owner: matfantinel
- Created: 2026-05-18T16:14:39.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-25T12:14:56.000Z (30 days ago)
- Last Synced: 2026-06-25T14:09:19.967Z (30 days ago)
- Topics: carousel, css, scroll-snap, slider
- Language: Svelte
- Homepage: https://carouselpilot.fantinel.dev
- Size: 1.11 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 🎠Carousel Pilot
[](//npmjs.com/package/carousel-pilot)
A zero-dependency, framework-agnostic web component that enhances your existing scrollable content with carousel functionality. The Carousel is yours, but Carousel Pilot handles navigation (prev/next), active state, autoplay, infinite loop and scroll tracking.
## Why?
Carousels are hard. You either use a heavy JS-based library or you gotta build things on your own. [CSS scroll snapping](https://fantinel.dev/blog/css-scroll-snapping/) has made it easier to build lightweight carousels, but some functionality like having the slides loop around are still heavily dependent on JS, and notably a pain to implement.
With Carousel Pilot you can have the both of best worlds: the scrolling and snapping is still handled by your CSS, responsive and reliable, but, [if JavaScript is available](https://www.kryogenix.org/code/browser/everyonehasjs.html), you can have Carousel Pilot jump in and add the extra goodies it provides, including a smooth infinite loop experience.
The cost? **17.7kB** (gzipped). It's not the most lightweight option out there, but hopefully it's the most straightforward one to use.
## How it works
Carousel Pilot is a [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) (``) that wraps your existing scrollable track. It doesn't impose any layout or styles — you style the track and slides however you want (flexbox, CSS scroll snap, etc.) and Carousel Pilot wires up the behavior.
It is built with [Svelte 5](https://svelte.dev/), but works with any framework or no framework at all.
## Installation
```bash
npm install carousel-pilot
```
## Usage
### With a bundler (Vite, Webpack, Rollup, etc.)
Import once anywhere in your app — this registers the custom element globally:
```js
import 'carousel-pilot';
```
### In a Svelte project
```js
import 'carousel-pilot';
// or, to use the Svelte component directly:
import { CarouselPilot } from 'carousel-pilot';
```
### Via CDN / plain HTML
```html
```
## Basic example
The only required piece is a scrollable track element marked with `data-carousel-track`. Everything else is optional.
```html
- Slide 1
- Slide 2
- Slide 3
Prev
Slide 1 of 3
Next
```
You are responsible for the track's CSS layout. A typical setup with CSS scroll snap:
```css
[data-carousel-track] {
display: flex;
gap: 1rem;
overflow: auto;
scroll-snap-type: x mandatory;
}
[data-carousel-track] > li {
flex: 0 0 300px;
scroll-snap-align: start;
}
```
## Slot conventions
Mark elements inside `` with these data attributes to connect them:
| Attribute | Role |
|---|---|
| `data-carousel-track` | The scrollable slide container (required) |
| `data-carousel-prev` | Button to scroll to the previous slide |
| `data-carousel-next` | Button to scroll to the next slide |
| `data-carousel-indicator` | Repeated indicator elements (e.g. dots); the active one gets an `active` class |
| `data-carousel-currentIndex` | Element whose text content is updated with the current slide number (1-based) |
## Props
### Selectors
These props accept a CSS selector string. Each one falls back to its corresponding `data-*` attribute if left empty.
| Prop | Default fallback | Description |
|---|---|---|
| `track` | `[data-carousel-track]` | The scrollable slide container. Required. |
| `prev` | `[data-carousel-prev]` | Button to scroll to the previous slide. |
| `next` | `[data-carousel-next]` | Button to scroll to the next slide. |
| `indicators` | `[data-carousel-indicator]` | Repeated indicator elements (e.g. dots). The active one receives an `active` class. |
| `current` | `[data-carousel-currentIndex]` | Element whose text content is set to the current slide number (1-based). |
### Behavior
| Prop | Type | Default | Description |
|---|---|---|---|
| `centered` | `boolean` | `false` | Centers the active slide in the track. Use `scroll-snap-align: center` on slides when enabled. |
| `scrollAmount` | `'slide' \| 'page'` | `'slide'` | How far prev/next scroll. `'slide'` uses the active slide's width; `'page'` uses the track's full width. |
### CSS injection
| Prop | Type | Default | Description |
|---|---|---|---|
| `addSpacers` | `boolean` | `true` | Injects invisible spacer elements so the first and last slides can be scrolled flush to the edge (or center when `centered` is on). |
| `showScrollShadow` | `boolean` | `false` | Injects a CSS scroll shadow on the track to hint that more content is scrollable. |
| `hideScrollbar` | `boolean` | `false` | Hides the track's scrollbar via injected CSS. |
### Autoplay
| Prop | Type | Default | Description |
|---|---|---|---|
| `autoplay` | `boolean` | `false` | Automatically advances slides on an interval. Pauses on hover, stops permanently on manual scroll or prev/next interaction. |
| `autoplayDelay` | `number` | `5000` | Interval in milliseconds between autoplay advances. |
### Loop
| Prop | Type | Default | Description |
|---|---|---|---|
| `loop` | `boolean` | `false` | Enables infinite looping by cloning slides on each side of the track. |
| `dedupeHeadings` | `boolean` | `true` | Replaces heading elements inside cloned slides with `
| `headingClasses` | `string` | `'h1, h2, h3, h4, h5, h6'` | Comma-separated class names applied to heading replacements in clones (index 0 → h1 class, 1 → h2, etc.). |
## JavaScript API
```js
const carousel = document.querySelector('carousel-pilot');
carousel.scrollToIndex(2); // scroll to a specific slide (0-based)
carousel.scrollNext(); // scroll forward one slide
carousel.scrollPrev(); // scroll backward one slide
```
## Events
```js
carousel.addEventListener('slide-change', (e) => {
console.log(e.detail.index); // 0-based index of the new active slide
console.log(e.detail.total); // total number of slides
});
carousel.addEventListener('autoplay-started', () => { /* ... */ });
carousel.addEventListener('autoplay-stopped', (e) => {
console.log(e.detail.paused); // true = paused (hovering), false = stopped permanently
});
```
## Development
The project uses [Storybook](https://storybook.js.org/) for local development. Example components live in `src/lib/examples/`.
```bash
npm install
npm run storybook
```
Storybook will start at **http://localhost:6006**.
## Building & publishing
Build both the Svelte library distribution and the standalone bundle:
```bash
npm run prepack
```
This runs `svelte-package` (for Svelte users) and a Vite library build (for everyone else), then validates the output with `publint`. Both outputs land in `dist/`.
Publish to npm:
```bash
npm publish
```
Make sure you've updated the `version` field in `package.json` before publishing.