react ~5 KB 0 deps v0.1.2 β†— GitHub β†—

@mshafiqyajid/react-carousel

Headless carousel hook and styled slide component for React. Touch swipe, drag, keyboard navigation, autoplay with pause-on-hover/focus, fade or slide transition, peek adjacent slides, counter overlay, autoplay progress bar, multi-slide view, dots, arrows, controlled/uncontrolled index, full ARIA compliance, and smooth motion with reduced-motion support.

Playground #

Mountain peak above the clouds at golden hour

Swiss Alps β€” golden hour above the clouds

Props
TSX
import { CarouselStyled } from "@mshafiqyajid/react-carousel/styled";
import "@mshafiqyajid/react-carousel/styles.css";

<CarouselStyled
  items={slides}
/>

Install #

npm install @mshafiqyajid/react-carousel

Quick start #

import { CarouselStyled } from "@mshafiqyajid/react-carousel/styled";
import "@mshafiqyajid/react-carousel/styles.css";

const SLIDES = [
  {
    src: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&q=80",
    alt: "Swiss Alps at golden hour",
    caption: "Swiss Alps β€” golden hour above the clouds",
  },
  {
    src: "https://images.unsplash.com/photo-1493246507139-91e8fad9978e?w=800&q=80",
    alt: "Moraine Lake, Banff",
    caption: "Moraine Lake, Banff β€” Canadian Rockies",
  },
  {
    src: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800&q=80",
    alt: "Morning forest light",
    caption: "Morning light, Black Forest, Germany",
  },
];

const slides = SLIDES.map((s) => (
  <div key={s.src} style={{ position: "relative", width: "100%", height: "100%" }}>
    <img
      src={s.src}
      alt={s.alt}
      style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
    />
    <div style={{
      position: "absolute", bottom: 0, left: 0, right: 0,
      padding: "2rem 1.25rem 1rem",
      background: "linear-gradient(to top, rgba(0,0,0,0.55), transparent)",
    }}>
      <p style={{ margin: 0, color: "#fff", fontSize: "0.8125rem", fontWeight: 500 }}>
        {s.caption}
      </p>
    </div>
  </div>
));

export function Gallery() {
  return (
    <div style={{ width: "100%", height: 360, borderRadius: 12, overflow: "hidden" }}>
      <CarouselStyled
        items={slides}
        loop
        showDots
        showArrows
        autoPlay
        autoPlayInterval={4000}
        counter
        style={{ width: "100%", height: "100%" }}
      />
    </div>
  );
}

Controlled index #

Use index and onIndexChange for full control over the active slide.

import { useState } from "react";
import { CarouselStyled } from "@mshafiqyajid/react-carousel/styled";
import "@mshafiqyajid/react-carousel/styles.css";

export function ControlledCarousel() {
  const [index, setIndex] = useState(0);
  return (
    <>
      <p>Active: {index}</p>
      <CarouselStyled
        items={slides}
        index={index}
        onIndexChange={setIndex}
      />
    </>
  );
}

Multi-slide view #

Show multiple slides at once with slidesPerView and spacing.

<CarouselStyled
  items={slides}
  slidesPerView={2}
  spacing={16}
  loop
/>

Headless usage #

import { useCarousel } from "@mshafiqyajid/react-carousel";

function MyCarousel({ items }) {
  const {
    containerProps,
    trackProps,
    getSlideProps,
    prevProps,
    nextProps,
    getDotProps,
    index,
    total,
  } = useCarousel({
    items,
    loop: true,
    autoPlay: true,
    autoPlayInterval: 3000,
  });

  return (
    <div {...containerProps} style={{ overflow: "hidden", position: "relative" }}>
      <div {...trackProps} style={{ display: "flex", ...trackProps.style }}>
        {items.map((item, i) => (
          <div key={i} {...getSlideProps(i)}>{item}</div>
        ))}
      </div>

      <button {...prevProps}>←</button>
      <button {...nextProps}>β†’</button>

      <div>
        {Array.from({ length: total }, (_, i) => (
          <button key={i} {...getDotProps(i)}>
            {i === index ? "●" : "β—‹"}
          </button>
        ))}
      </div>
    </div>
  );
}

Keyboard #

KeyAction
ArrowLeft / ArrowRightPrevious / next slide (horizontal)
ArrowUp / ArrowDownPrevious / next slide (vertical)
HomeFirst slide
EndLast slide
TabFocus prev/next buttons

Props #

PropTypeDefaultDescription
itemsReactNode[]β€”Slides to render
indexnumberβ€”Controlled active index
defaultIndexnumber0Uncontrolled initial index
onIndexChange(index: number) => voidβ€”Called when index changes
orientation"horizontal" | "vertical""horizontal"Slide axis
loopbooleantrueWrap around at boundaries
autoPlaybooleanfalseAuto-advance slides
autoPlayIntervalnumber4000Auto-advance interval in ms
pauseOnHoverbooleantruePause autoPlay on hover
pauseOnFocusbooleantruePause autoPlay on focus
slidesPerViewnumber1Slides visible at once
spacingnumber | string0Gap between slides
showDotsbooleantrueShow dot indicators
showArrowsbooleantrueShow prev/next arrow buttons
swipebooleantrueEnable touch swipe
dragbooleantrueEnable mouse drag
dragThresholdnumber50Min px distance to commit swipe
onSwipeStart() => voidβ€”Called when drag starts
onSwipeEnd(committed: boolean) => voidβ€”Called when drag ends
renderDot(index, active) => ReactNodeβ€”Custom dot renderer
renderArrow(dir, disabled) => ReactNodeβ€”Custom arrow renderer
classNamestringβ€”Extra class on root element
styleCSSPropertiesβ€”Inline style on root element
refRef<HTMLDivElement>β€”Forwarded to root wrapper div
transitionEffect"slide" | "fade""slide"Slide transition style
peeknumber | string0Show a sliver of adjacent slides (px or CSS string like "10%")
counterbooleanfalseShow "N / M" counter overlay (top-right)
autoPlayProgressbooleanfalseShow a progress bar filling over each autoPlay interval

Fade transition #

Set transitionEffect="fade" to crossfade between slides instead of sliding. Drag/swipe still works β€” dragging past the threshold commits the slide change.

<CarouselStyled
  items={slides}
  transitionEffect="fade"
  showDots
  autoPlay
/>

Peek #

Pass a peek value (number in px or a CSS string) to reveal the edges of the previous and next slides. The root element still clips, but the viewport lets adjacent slides bleed through.

<CarouselStyled
  items={slides}
  peek={40}
  loop
/>

Counter overlay #

Enable counter to show a frosted-glass "N / M" pill in the top-right corner β€” useful when dots are hidden or as an additional visual cue.

<CarouselStyled
  items={slides}
  counter
  showDots={false}
/>

AutoPlay progress bar #

When autoPlay is enabled, add autoPlayProgress to show a thin bar at the bottom of the carousel that fills over the interval. It resets on each slide change and pauses when the carousel is paused.

<CarouselStyled
  items={slides}
  autoPlay
  autoPlayInterval={4000}
  autoPlayProgress
/>

CSS variables #

:root {
  --rcar-radius: 12px;
  --rcar-arrow-size: 2.5rem;
  --rcar-dot-size: 8px;
  --rcar-dot-size-active: 24px;
  --rcar-dot-gap: 6px;
  --rcar-arrow-bg: rgba(255, 255, 255, 0.92);
  --rcar-arrow-fg: #18181b;
  --rcar-dot-bg: rgba(0, 0, 0, 0.22);
  --rcar-dot-bg-active: #18181b;
  --rcar-duration: 300ms;
  --rcar-ease: cubic-bezier(0.32, 0.72, 0, 1);
}
Edit this page on GitHub