CSS-first slider for the modern web
bun add aero-slider Interact with the slider to see events
Examples
Multi-Slide View
Show multiple slides at once with gap spacing.
Viewport Breakout
Let slides overflow past the content column while clipping at the page edge. This demo keeps the slider aligned to the docs content width, but adds internal gutter padding so the active card never sits flush against the screen.
Set the outer wrapper to full bleed, add the library's
aero-slider--breakout
class, then put the docs-specific sizing tokens on that slider instance to keep slide
sizing tied to the normal content column with
--aero-layout-width,
and set
--aero-edge-inset
so the first and last snapped slides line up with the page gutter.
:root {
--page-gutter: 1rem;
}
@media (min-width: 640px) {
:root {
--page-gutter: 1.5rem;
}
}
body {
overflow-x: hidden;
}
.docs-breakout-clip {
width: 100vw;
margin-left: calc(50% - 50vw);
}
.aero-slider--viewport-breakout-demo {
--slides-per-view: 1.12;
--slide-gap: 12px;
--aero-edge-inset: var(--page-gutter);
--aero-layout-width: min(72rem, calc(100vw - var(--page-gutter) * 2));
}
@media (min-width: 640px) {
.aero-slider--viewport-breakout-demo {
--slides-per-view: 1.35;
--slide-gap: 18px;
}
} Alignment
Use alignment
to snap the active slide to the left, center, or right side of the viewport. Center is
the default.
Left
Center
Right
createSlider(leftEl, { alignment: "left", loop: true });
createSlider(centerEl, { alignment: "center", loop: true });
createSlider(rightEl, { alignment: "right", loop: true }); Responsive with Media Queries
Layout adapts to viewport: 1 slide on mobile, 2 on tablet, 3 on desktop. Resize the
window to see the slider re-read
--slides-per-view
from CSS.
/* 1 slide on mobile, 2 on tablet, 3 on desktop */
.aero-slider--responsive {
--slides-per-view: 1;
--slide-gap: 8px;
}
@media (min-width: 640px) {
.aero-slider--responsive {
--slides-per-view: 2;
--slide-gap: 12px;
}
}
@media (min-width: 1024px) {
.aero-slider--responsive {
--slides-per-view: 3;
--slide-gap: 16px;
}
} Autoplay + Loop
Auto-advances every 3 seconds with infinite looping. Pauses on hover and drag.
Thumbnail Sync
A main slider plus a thumbnail strip: clicking a thumbnail navigates the main slider,
and when you change slides (drag, arrows, etc.), the thumbnail strip scrolls to keep the
active thumb in view. Use the
syncThumbnails()
function — both sliders must have the same number of slides. If you want the active
thumb centered, set
alignment: "center"
on the thumbnail slider.
How it works
Create two sliders: the main (primary) slider and a thumbnail slider with the same
number of slides. Call
syncThumbnails(primary, thumbnail)
to wire them up. The function adds click handlers to thumbnails, listens to
slideChange events
on the primary, scrolls the thumbnail strip to keep the active one in view, and adds
aero-slider__thumb--active
to the active thumbnail. It returns a teardown function — call it before destroying the
sliders.
HTML — main slider
<div id="main-slider" class="aero-slider">
<div class="aero-slider__viewport relative">
<button type="button" class="aero-slider__nav--prev" aria-label="Previous">…</button>
<button type="button" class="aero-slider__nav--next" aria-label="Next">…</button>
<div class="aero-slider__track">
<div>Slide 1</div>
<div>Slide 2</div>
<div>Slide 3</div>
</div>
</div>
</div> HTML — thumbnail slider
<div id="thumb-slider" class="aero-slider" style="--slides-per-view: 3; --slide-gap: 8px;">
<div class="aero-slider__viewport">
<div class="aero-slider__track">
<div>Thumb 1</div>
<div>Thumb 2</div>
<div>Thumb 3</div>
</div>
</div>
</div> JavaScript
import { createSlider, syncThumbnails } from "aero-slider";
const main = createSlider(document.getElementById("main-slider"));
const thumbs = createSlider(document.getElementById("thumb-slider"), {
alignment: "center",
});
const teardown = syncThumbnails(main, thumbs);
// On unmount:
// teardown();
// main.destroy();
// thumbs.destroy(); Direction
Set direction
to "rtl" for
right-to-left or
"ttb" for
vertical (top-to-bottom). Keyboard arrows, drag axis, and scroll axis all adapt
automatically.
RTL
Vertical (ttb)
// RTL
createSlider(rtlEl, { direction: "rtl" });
// Vertical
createSlider(verticalEl, { direction: "ttb" }); Dynamic Slides
Use add() and
remove() to
dynamically insert or remove slides. The slider rebuilds pagination, navigation, and
loop clones automatically.
const slider = createSlider(el);
// Add a new slide at the end
const slide = document.createElement("div");
slide.textContent = "New";
slider.add(slide);
// Add at a specific position
slider.add(slide, 2);
// Remove the last slide
slider.remove(slider.slideCount - 1);
// Remove multiple slides
slider.remove([0, 1]); Per-Move
Set perMove
to control how many slides advance per arrow/keyboard press. Here, 3 slides are visible
but each press moves by 2, with looping enabled.
createSlider(el, { perMove: 2, loop: true });
// With CSS: --slides-per-view: 3; --slide-gap: 12px; API Reference
createSlider(element, config?)
Creates a new slider instance on the given container element. Pagination and navigation are configured via DOM structure, not JavaScript—see the Pagination & Navigation example for required HTML.
import { createSlider } from "aero-slider";
import "aero-slider/slider.css";
const slider = createSlider(document.getElementById("my-slider"), {
loop: true,
draggable: true,
}); Without a bundler: Use the global script — see window.AeroSlider.
window.AeroSlider (global script)
When using the standalone script (no bundler), the library attaches an
AeroSlider
object to window
with two methods: createSlider and
syncThumbnails.
Include the CSS and JS files, then call AeroSlider.createSlider() and optionally
AeroSlider.syncThumbnails().
Use aero-slider.min.js with
type="module"
from unpkg
or the releases page.
<link rel="stylesheet" href="https://unpkg.com/aero-slider/dist/aero-slider.min.css" />
<script type="module" src="https://unpkg.com/aero-slider/dist/aero-slider.min.js"></script>
<script type="module">
// AeroSlider is on window after the module loads
const slider = AeroSlider.createSlider(document.getElementById("my-slider"), { loop: true });
</script> With thumbnails:
const main = AeroSlider.createSlider(mainEl);
const thumbs = AeroSlider.createSlider(thumbsEl);
const teardown = AeroSlider.syncThumbnails(main, thumbs);
// On unmount: teardown(); main.destroy(); thumbs.destroy(); | Property | Description |
|---|---|
| AeroSlider.createSlider | Same as the module createSlider — creates a slider instance |
| AeroSlider.syncThumbnails | Same as the module syncThumbnails — syncs primary and thumbnail sliders |
CSS Custom Properties
Layout is controlled via CSS custom properties on the container.
| Property | Default | Description |
|---|---|---|
| --slides-per-view | 1 | Number of visible slides (supports fractional, e.g. 1.5) |
| --slide-gap | 0px | Gap between slides |
| --slide-aspect | 16 / 9 | Aspect ratio for slides |
| data-aero-defer-visibility | — | Prevents layout shift before JS init in loop mode by reserving space for prepended clones. Pair it with data-aero-alignment in HTML when mirroring a non-default pre-init alignment; center is assumed otherwise. |
SliderConfig
All configuration options (all optional with sensible defaults).
| Option | Type | Default | Description |
|---|---|---|---|
| loop | boolean | false | Infinite looping |
| alignment | "left" | "center" | "right" | "center" | Align the active slide to the left, center, or right when possible. At non-loop edges the scroll position clamps, but currentIndex still reflects the focused slide. |
| draggable | boolean | true | Enable drag/swipe |
| autoplay | boolean | false | Auto-advance slides |
| autoplayInterval | number | 5000 | Delay between auto-advances (ms) |
| maxDots | number | undefined | Max pagination dots; shows iOS-style sliding window when exceeded |
| noDrag | string | "" | CSS selector for elements inside slides that should not trigger drag (e.g. "a, button, input") |
| perMove | number | 1 | Number of slides to advance per next() / prev() call |
| direction | "ltr" | "rtl" | "ttb" | "ltr" | Carousel direction: left-to-right, right-to-left, or top-to-bottom (vertical) |
SliderInstance
Returned by createSlider().
The instance is also attached to the container element. You can access it via container.aeroSlider when you have a reference to the DOM element.
| Method / Property | Description |
|---|---|
| .next() | Go to next slide |
| .prev() | Go to previous slide |
| .goTo(index) | Jump to a specific slide index |
| .update(config?) | Update config and re-render |
| .destroy() | Tear down and clean up |
| .refresh() | Re-read slides from DOM and rebuild (call after external DOM changes) |
| .add(slides, index?) | Insert one or more slides into the track; calls refresh() automatically |
| .remove(index) | Remove slide(s) by index (number or array); calls refresh() automatically |
| .on(event, cb) | Subscribe to an event |
| .off(event, cb) | Unsubscribe from an event |
| .currentIndex | Current active slide index |
| .slideCount | Total number of slides |
For syncing a main slider with a thumbnail strip, use the standalone
syncThumbnails(primary, thumbnail)
function (see below).
syncThumbnails(primary, thumbnail)
Syncs a primary slider with a thumbnail slider. Clicking a thumbnail navigates the
primary to that slide; when the primary changes (drag, arrows, etc.), the thumbnail
slider scrolls to keep the active thumbnail in view and adds
aero-slider__thumb--active
to it. The function does not force thumbnail alignment; set
alignment: "center"
on the thumbnail slider if you want the active thumb centered when possible. Both
sliders must have the same number of slides. Returns a teardown function — call it
before destroying the sliders.
import { createSlider, syncThumbnails } from "aero-slider";
const main = createSlider(mainEl);
const thumbs = createSlider(thumbsEl, {
alignment: "center",
});
const teardown = syncThumbnails(main, thumbs);
// On unmount:
// teardown();
// main.destroy();
// thumbs.destroy(); Events
Aero Slider dispatches native DOM events on the container element, prefixed with aero:.
Event data is available via event.detail.
Access the slider instance via event.currentTarget.aeroSlider in any event handler.
| Event | Data (event.detail, TS type) | Description |
|---|---|---|
| aero:ready | {} | Fired once after initialization (listen before createSlider) |
| aero:slideChange | { index: number } | Fired when the active slide changes |
| aero:dragStart | { index: number } | Fired when the user starts dragging |
| aero:dragEnd | { index: number; fromIndex: number } | Fired when the user stops dragging |
| aero:autoplayStart | {} | Fired when autoplay starts or resumes |
| aero:autoplayStop | {} | Fired when autoplay pauses |
| aero:visible | { index: number } | Fired per slide when it enters the visible range |
| aero:hidden | { index: number } | Fired per slide when it leaves the visible range |
| aero:resize | {} | Fired when a CSS variable change is detected (before re-measuring) |
| aero:resized | {} | Fired after the slider finishes updating from a resize |
| aero:destroy | {} | Fired at the start of destroy(), before teardown |
Browser Support
Aero Slider relies on modern CSS features to achieve a lightweight, JS-free layout. It degrades gracefully on older browsers, but for the optimal experience, the following minimum versions are recommended:
| CSS Feature | Chrome | Safari | Firefox | Used For |
|---|---|---|---|---|
| CSS Variables | 49+ | 9.1+ | 31+ | Configuring layout and gap size. |
| Scroll Snapping | 69+ | 11+ | 68+ | Locking slides into place after scrolling. |
| aspect-ratio | 88+ | 15+ | 89+ | Maintaining proper height for the slider track. |
| contain: paint | 52+ | 15.4+ | 69+ | Performance optimization for painting slides. |
| overscroll-behavior-x | 63+ | 16+ | 59+ | Preventing rubber-banding when hitting the edge. |