npm package: @ghmm/next-carouslider
Description: Custom scroll-based Slider and animation-based CarouselAnimation for React/Next.js projects
Latest version: 1.4.3

# 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 `!important` needed
- **Touch & mouse drag** with momentum scrolling
- **Fully accessible** (ARIA roles, labels, keyboard-friendly)
- **Zero Next.js config** — no `transpilePackages` needed, works out of the box

---

## Installation

```bash
npm install @ghmm/next-carouslider
# or
yarn add @ghmm/next-carouslider
# or
pnpm add @ghmm/next-carouslider
```

### Import the CSS

Import the base stylesheet **once** in your app (e.g. `layout.tsx`, `_app.tsx` or your global CSS):

```ts
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:

```tsx
'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

```tsx
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 `itemWidth` nor `slidesPerView` is set, each slide fills the full viewport width (banner mode).

### Ref methods

```tsx
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 value
```

### CSS 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

```css
.pgx-slider {
  border-radius: 12px;
  overflow: hidden;
}
```

#### Style each slide item

```css
.pgx-slider__item {
  border-radius: 8px;
  overflow: hidden;
}
```

#### Customize the nav buttons

```css
.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

```tsx
<Slider ariaLabel="Gallery" className="my-gallery-slider" gap={12} slidesPerView={2}>
  ...
</Slider>
```

```css
.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

```tsx
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

```tsx
<CarouselAnimation
  ariaLabel="Autoplay example"
  autoplay={{ mode: 'step', intervalMs: 3000 }}
>
  ...
</CarouselAnimation>
```

```tsx
// 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

```tsx
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 index
```

### Custom dots

```tsx
<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

```tsx
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:

```css
:root {
  --pgx-dot-color: #ccc;          /* inactive dot color */
  --pgx-dot-active-color: #1a1a2e; /* active dot color */
}
```

Or scope them to your carousel:

```css
.my-carousel {
  --pgx-dot-color: rgba(255, 255, 255, 0.4);
  --pgx-dot-active-color: white;
}
```

```tsx
<CarouselAnimation ariaLabel="Themed" className="my-carousel" showDots>
  ...
</CarouselAnimation>
```

### Styling examples

#### Style the viewport (e.g. add border-radius)

```css
.pgx-carousel__viewport {
  border-radius: 16px;
}
```

#### Style individual slides

```css
.pgx-carousel__slide {
  padding: 0 8px;
}
```

#### Customize buttons

```css
.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

```tsx
<CarouselAnimation ariaLabel="Hero" className="hero-carousel" showPrevNext showDots>
  ...
</CarouselAnimation>
```

```css
.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

```ts
import type {
  SliderProps,
  SliderRef,
  SliderLoopMode,
  CarouselAnimationProps,
  CarouselAnimationRef,
  CarouselAnimationAutoplay,
  CarouselAnimationAutoplayMode,
  CarouselAnimationAlign,
  CarouselAnimationLoopMode,
} from '@ghmm/next-carouslider'
```

---

## License

MIT