@ghmm/next-carouslider
v1.4.3
Published
Custom scroll-based Slider and animation-based CarouselAnimation for React/Next.js projects
Downloads
426
Readme
Caroulslider
A zero-dependency, CSS-first slider and carousel library for React/Next.js.
- No Tailwind required — all styles live in a single importable CSS file
- BEM class names on every element — override anything with plain CSS, no
!importantneeded - Touch & mouse drag with momentum scrolling
- Fully accessible (ARIA roles, labels, keyboard-friendly)
- Zero Next.js config — no
transpilePackagesneeded, works out of the box
Installation
npm install @ghmm/next-carouslider
# or
yarn add @ghmm/next-carouslider
# or
pnpm add @ghmm/next-carousliderImport the CSS
Import the base stylesheet once in your app (e.g. layout.tsx, _app.tsx or your global CSS):
import '@ghmm/next-carouslider/styles.css'All layout and default visual styles live in this file. Every class uses standard CSS specificity — override anything with plain CSS, no !important needed.
Next.js App Router
Since the components use React hooks, they must run on the client. Just mark your file (or a wrapper component) with "use client" — no transpilePackages or any other config needed:
'use client'
import { Slider } from '@ghmm/next-carouslider'
import '@ghmm/next-carouslider/styles.css'
export function MySlider() {
return (
<Slider ariaLabel="My slider" gap={16} slidesPerView={3}>
<div>Item 1</div>
<div>Item 2</div>
</Slider>
)
}Components
| Component | Description |
|---|---|
| <Slider> | Scroll-based horizontal slider with momentum drag |
| <CarouselAnimation> | Animation-based carousel (steps, slide, smooth) with dots, autoplay, and more |
Slider
A performant, scroll-driven slider. Items can be dragged with mouse or touch, with momentum. Supports infinite looping, configurable gap, item width or slides-per-view.
Basic usage
import { Slider } from '@ghmm/next-carouslider'
export function MySlider() {
return (
<Slider ariaLabel="My slider" gap={16} slidesPerView={3}>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
</Slider>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| ariaLabel | string | — | Accessible label for the slider region |
| gap | number | 0 | Gap in px between slides |
| loopMode | 'default' \| 'infinite' | 'default' | Scroll loop behavior |
| showPrevNext | boolean | false | Show prev/next arrow buttons |
| itemWidth | number | — | Fixed width in px for each slide (mutually exclusive with slidesPerView) |
| slidesPerView | number | — | Number of slides visible at once (mutually exclusive with itemWidth) |
| className | string | — | Extra CSS class on the outer wrapper |
If neither
itemWidthnorslidesPerViewis set, each slide fills the full viewport width (banner mode).
Ref methods
import { useRef } from 'react'
import { Slider, SliderRef } from '@ghmm/next-carouslider'
const ref = useRef<SliderRef>(null)
ref.current?.next() // scroll to next slide
ref.current?.prev() // scroll to previous slide
ref.current?.goTo(2) // scroll to slide index 2
ref.current?.getActiveIndex() // returns current active index
ref.current?.getScrollLeft() // returns current scrollLeft valueCSS class names (BEM)
Use these classes to style the Slider from your own CSS:
| Class | Element |
|---|---|
| .pgx-slider | Outer wrapper (div) |
| .pgx-slider__viewport | Scrollable viewport (div) |
| .pgx-slider__track | Flex track holding all items (div) |
| .pgx-slider__item | Individual slide wrapper (div) |
| .pgx-slider__nav | Prev/next buttons container (div) |
| .pgx-slider__btn | Prev and next buttons (button) |
| .pgx-slider__btn--prev | Previous button |
| .pgx-slider__btn--next | Next button |
| .pgx-slider__btn--disabled | Button when it is disabled |
Styling examples
Style the outer wrapper
.pgx-slider {
border-radius: 12px;
overflow: hidden;
}Style each slide item
.pgx-slider__item {
border-radius: 8px;
overflow: hidden;
}Customize the nav buttons
.pgx-slider__btn {
background: white !important;
color: black !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.pgx-slider__btn--disabled {
display: none;
}Style with your own className
<Slider ariaLabel="Gallery" className="my-gallery-slider" gap={12} slidesPerView={2}>
...
</Slider>.my-gallery-slider {
padding: 0 24px;
}
.my-gallery-slider .pgx-slider__btn {
background: rgba(0, 0, 0, 0.5);
color: white;
}CarouselAnimation
An animation-based carousel supporting multiple animation modes, dots, autoplay (step or marquee), controlled index, and infinite looping.
Basic usage
import { CarouselAnimation } from '@ghmm/next-carouslider'
export function MyCarousel() {
return (
<CarouselAnimation ariaLabel="My carousel" showDots showPrevNext>
<img src="/slide1.jpg" alt="Slide 1" />
<img src="/slide2.jpg" alt="Slide 2" />
<img src="/slide3.jpg" alt="Slide 3" />
</CarouselAnimation>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| ariaLabel | string | — | Accessible label for the carousel region |
| activeIndex | number | — | Controlled active slide index |
| onIndexChange | (index: number) => void | — | Callback when active index changes |
| loopMode | 'default' \| 'loop' \| 'infinite' | 'default' | Loop behavior ('loop' wraps on next/prev, 'infinite' clones slides for seamless scroll) |
| showPrevNext | boolean | false | Show prev/next arrow buttons |
| showDots | boolean | false | Show pagination dots |
| renderDots | (params) => ReactNode | — | Custom dot renderer (see below) |
| animation | 'steps' \| 'slide' \| 'smooth' | 'steps' | Animation mode |
| transitionDurationMs | number | 280 | Slide transition duration in ms |
| stepZoomOutMs | number | 140 | Duration of zoom-out phase (steps mode) |
| stepZoomInMs | number | 140 | Duration of zoom-in phase (steps mode) |
| dragThreshold | number | 10 | Minimum drag distance in px to register a swipe |
| viewportBackground | string | 'transparent' | Background of the viewport (useful for steps animation) |
| align | 'start' \| 'center' \| 'end' | 'center' | Horizontal alignment of slides within the track |
| gap | number | 0 | Gap in px between slides |
| itemWidth | number | — | Fixed width in px per slide (strip mode) |
| slidesPerView | number | — | Number of visible slides (strip mode) |
| autoplay | CarouselAnimationAutoplay | — | Autoplay config (see below) |
| className | string | — | Extra CSS class on the outer wrapper |
Animation modes
| Value | Description |
|---|---|
| 'steps' | Zoom out → slide → zoom in (default) |
| 'slide' | Clean horizontal slide with easing |
| 'smooth' | Slide with a slight scale effect on transition |
Autoplay config
<CarouselAnimation
ariaLabel="Autoplay example"
autoplay={{ mode: 'step', intervalMs: 3000 }}
>
...
</CarouselAnimation>// Marquee (continuous scroll)
<CarouselAnimation
ariaLabel="Marquee example"
autoplay={{ mode: 'marquee', intervalMs: 0, speedPxPerSecond: 60 }}
loopMode="infinite"
>
...
</CarouselAnimation>| Field | Type | Description |
|---|---|---|
| mode | 'step' \| 'marquee' | Step advances one slide at a time; marquee scrolls continuously |
| intervalMs | number | Interval in ms between steps |
| speedPxPerSecond | number | Pixels per second for marquee mode (default: 40) |
Ref methods
import { useRef } from 'react'
import { CarouselAnimation, CarouselAnimationRef } from '@ghmm/next-carouslider'
const ref = useRef<CarouselAnimationRef>(null)
ref.current?.next() // go to next slide
ref.current?.prev() // go to previous slide
ref.current?.goTo(1) // go to slide index 1
ref.current?.getActiveIndex() // returns current active indexCustom dots
<CarouselAnimation
ariaLabel="Custom dots"
showDots
renderDots={({ total, activeIndex, goTo }) => (
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginTop: 12 }}>
{Array.from({ length: total }, (_, i) => (
<button
key={i}
onClick={() => goTo(i)}
style={{
width: i === activeIndex ? 24 : 8,
height: 8,
borderRadius: 4,
border: 'none',
background: i === activeIndex ? '#333' : '#ccc',
transition: 'width 150ms ease',
cursor: 'pointer',
}}
/>
))}
</div>
)}
>
...
</CarouselAnimation>Controlled carousel
import { useState } from 'react'
import { CarouselAnimation } from '@ghmm/next-carouslider'
export function ControlledCarousel() {
const [index, setIndex] = useState(0)
return (
<>
<CarouselAnimation
ariaLabel="Controlled carousel"
activeIndex={index}
onIndexChange={setIndex}
>
<div>Slide A</div>
<div>Slide B</div>
<div>Slide C</div>
</CarouselAnimation>
<div style={{ display: 'flex', gap: 8, marginTop: 16, justifyContent: 'center' }}>
{['A', 'B', 'C'].map((label, i) => (
<button key={i} onClick={() => setIndex(i)}>
{label}
</button>
))}
</div>
</>
)
}CSS class names (BEM)
Use these classes to style the CarouselAnimation from your own CSS:
| Class | Element |
|---|---|
| .pgx-carousel | Outer wrapper (section) |
| .pgx-carousel__viewport | Overflow-hidden viewport (div) |
| .pgx-carousel__track | Transform track holding all slides (div) |
| .pgx-carousel__slide | Individual slide wrapper (div) |
| .pgx-carousel__slide--active | Currently active slide |
| .pgx-carousel__nav | Prev/next buttons container (div) |
| .pgx-carousel__btn | Prev and next buttons (button) |
| .pgx-carousel__btn--prev | Previous button |
| .pgx-carousel__btn--next | Next button |
| .pgx-carousel__btn--disabled | Button when disabled |
| .pgx-carousel__dots | Dots container (div) |
| .pgx-carousel__dot | Individual dot button (button) |
| .pgx-carousel__dot--active | Active dot |
CSS variables for dots
The default dots use CSS variables so you can theme them without overriding inline styles:
:root {
--pgx-dot-color: #ccc; /* inactive dot color */
--pgx-dot-active-color: #1a1a2e; /* active dot color */
}Or scope them to your carousel:
.my-carousel {
--pgx-dot-color: rgba(255, 255, 255, 0.4);
--pgx-dot-active-color: white;
}<CarouselAnimation ariaLabel="Themed" className="my-carousel" showDots>
...
</CarouselAnimation>Styling examples
Style the viewport (e.g. add border-radius)
.pgx-carousel__viewport {
border-radius: 16px;
}Style individual slides
.pgx-carousel__slide {
padding: 0 8px;
}Customize buttons
.pgx-carousel__btn {
background: rgba(255, 255, 255, 0.9) !important;
color: #333 !important;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
width: 48px !important;
height: 48px !important;
}
.pgx-carousel__btn--disabled {
opacity: 0 !important;
}Use className for scoped overrides
<CarouselAnimation ariaLabel="Hero" className="hero-carousel" showPrevNext showDots>
...
</CarouselAnimation>.hero-carousel .pgx-carousel__btn {
background: white;
color: black;
}
.hero-carousel .pgx-carousel__dots {
margin-top: 16px;
}
.hero-carousel .pgx-carousel__dot--active {
/* the active dot color is controlled via --pgx-dot-active-color */
}TypeScript types
import type {
SliderProps,
SliderRef,
SliderLoopMode,
CarouselAnimationProps,
CarouselAnimationRef,
CarouselAnimationAutoplay,
CarouselAnimationAutoplayMode,
CarouselAnimationAlign,
CarouselAnimationLoopMode,
} from '@ghmm/next-carouslider'License
MIT
