V1 coming soon ~6.5 KB gzipped

CSS-first slider for the modern web

CSS-First Architecture ~6.5 KB gzipped Zero Dependencies Highly Extensible Native Scroll Snapping Touch & Swipe Friendly Accessible Infinite Looping
Smooth Autoplay Multi-Slide Views Responsive API Custom Pagination Thumbnail Syncing Event Callbacks Zero CLS Keyboard Nav
.zip
bun add aero-slider
1 Slide One
2 Slide Two
3 Slide Three
4 Slide Four
5 Slide Five

Interact with the slider to see events

Examples

Multi-Slide View

Show multiple slides at once with gap spacing.

A
B
C
D
E
F

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.

The dashed lines mark the normal content column. The slider stays aligned to this width, but nearby slides can paint beyond it.
Aligned 01 The active card is still positioned from the main content column.
Bleed 02 The scroll area reaches the page edge, but slide sizing still keys off the content width.
Viewport 03 That makes the next and previous slides feel like they continue off-screen.
Host Layout 04 The site still owns the breakout wrapper and gutter math around the slider.
Scrollbars 05 The breakout width also subtracts classic scrollbar space so it lands on the visible edge.
: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

A
B
C
D

Center

A
B
C
D

Right

A
B
C
D
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 Mobile
2 Tablet
3 Desktop
4 Wide
5 Extra
/* 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.

🌙 Night
🌅 Sunset
☀️ Day
🌄 Dawn

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.

1 Main slide
2 Main slide
3 Main slide
4 Main slide
1
2
3
4

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)

1
2
3
// 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.

1
2
3
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.

1
2
3
4
5
6
7
8
9
10
11
12
createSlider(el, { perMove: 2, loop: true });
// With CSS: --slides-per-view: 3; --slide-gap: 12px;

Pagination & Navigation

Pagination and navigation are not configured through JavaScript. You create the DOM elements yourself. If they exist inside the container, the slider discovers and wires them automatically.

Pagination

A container with class aero-slider__pagination and one dot template with class aero-slider__dot. The slider clones the dot for each slide and manages active state via aero-slider__dot--active.

<div class="aero-slider">
  <div class="aero-slider__viewport relative">
    <div class="aero-slider__track">
      <div>Slide 1</div>
      <div>Slide 2</div>
      <div>Slide 3</div>
    </div>
  </div>
  <div class="aero-slider__pagination">
    <span class="aero-slider__dot"></span>
  </div>
</div>
1
2
3

Navigation

Two buttons inside the viewport (before the track): one with aero-slider__nav--prev and one with aero-slider__nav--next. Include aria-label for accessibility. The slider enables/disables them based on loop mode and current index.

Placing the buttons before the track in the DOM improves keyboard navigation—users reach the prev/next controls before tabbing through slide content.

<div class="aero-slider">
  <div class="aero-slider__viewport relative">
    <button type="button" class="aero-slider__nav--prev" aria-label="Previous slide">‹</button>
    <button type="button" class="aero-slider__nav--next" aria-label="Next slide">›</button>
    <div class="aero-slider__track">
      <div>Slide 1</div>
      <div>Slide 2</div>
      <div>Slide 3</div>
    </div>
  </div>
</div>

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.createSliderSame as the module createSlider — creates a slider instance
AeroSlider.syncThumbnailsSame 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-view1Number of visible slides (supports fractional, e.g. 1.5)
--slide-gap0pxGap between slides
--slide-aspect16 / 9Aspect ratio for slides
data-aero-defer-visibilityPrevents 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
loopbooleanfalseInfinite 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.
draggablebooleantrueEnable drag/swipe
autoplaybooleanfalseAuto-advance slides
autoplayIntervalnumber5000Delay between auto-advances (ms)
maxDotsnumberundefinedMax pagination dots; shows iOS-style sliding window when exceeded
noDragstring""CSS selector for elements inside slides that should not trigger drag (e.g. "a, button, input")
perMovenumber1Number 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
.currentIndexCurrent active slide index
.slideCountTotal 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.