@elvitech/carousel
v1.1.1
Published
A modern, animated, responsive, and fully customizable carousel component for React, built with TypeScript and Framer Motion.
Maintainers
Readme
@elvitech/carousel
A modern, animated, responsive, and fully customizable carousel for React — built with TypeScript and Framer Motion.
- 🎞️ 5 animation styles — slide, fade, zoom, cube, cards
- 📱 Responsive — per-breakpoint
slidesPerView/slidesToScroll/gap - 👆 Swipe & drag — pointer, touch, and mouse, with velocity-based flicks
- ▶️ Autoplay — with pause-on-hover and pause-on-focus
- ♿ Accessible — ARIA roles, keyboard navigation (arrows, Home/End),
prefers-reduced-motionsupport - 🎨 Customizable — CSS variables, custom arrows/dots via render props, controlled or uncontrolled index
- 🔁 Infinite loop, multi-item views, and an imperative ref API (
next/prev/goTo)
Install
npm install @elvitech/carousel framer-motionreact, react-dom, and framer-motion are peer dependencies — install them if your project doesn't already have them.
Quick start
import { Carousel } from "@elvitech/carousel";
import "@elvitech/carousel/styles.css";
function App() {
return (
<Carousel animation="fade" autoplay={4000} loop>
<img src="/one.jpg" alt="" />
<img src="/two.jpg" alt="" />
<img src="/three.jpg" alt="" />
</Carousel>
);
}Each direct child of <Carousel> becomes one slide — pass any React nodes.
Vertical carousel (top↔bottom)
<Carousel
direction="ttb" // or "btt" to reverse the travel direction
height={320} // required for multi-item vertical layouts
slidesPerView={1}
animation="slide"
>
{slides}
</Carousel>direction="ltr" | "rtl"→ horizontal carousel (default"ltr")direction="ttb" | "btt"→ vertical carousel; arrows move to top/bottom, drag/swipe and Up/Down arrow keys navigate instead of Left/Rightheightonly matters for vertical carousels withslidesPerView > 1— a column flex layout can't derive its own height from percentage-sized children, so pass a fixed pixel height. Single-slide vertical carousels size themselves from the slide content, same as horizontal ones.
Multi-item, responsive carousel
<Carousel
slidesPerView={3}
slidesToScroll={3}
gap={16}
breakpoints={[
{ maxWidth: 768, slidesPerView: 2, slidesToScroll: 2 },
{ maxWidth: 480, slidesPerView: 1, slidesToScroll: 1 }
]}
>
{products.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</Carousel>breakpoints are evaluated as "max viewport width" thresholds — the first one the current width fits under wins; otherwise the base props apply.
Imperative control
const carouselRef = useRef<CarouselHandle>(null);
<Carousel ref={carouselRef}>{slides}</Carousel>;
carouselRef.current?.next();
carouselRef.current?.prev();
carouselRef.current?.goTo(2);
carouselRef.current?.pause();
carouselRef.current?.play();
carouselRef.current?.activeIndex; // current indexCustom arrows & dots
<Carousel
renderPrevArrow={(onClick, disabled) => (
<button onClick={onClick} disabled={disabled}>
←
</button>
)}
renderNextArrow={(onClick, disabled) => (
<button onClick={onClick} disabled={disabled}>
→
</button>
)}
renderDots={({ count, activeIndex, goTo }) => (
<div>
{Array.from({ length: count }).map((_, i) => (
<span key={i} onClick={() => goTo(i)}>
{i === activeIndex ? "●" : "○"}
</span>
))}
</div>
)}
>
{slides}
</Carousel>Styling
The default stylesheet exposes CSS variables you can override on .rac-root (or pass style={{ "--rac-arrow-bg": "..." }} via the style prop):
.rac-root {
--rac-arrow-bg: rgba(255, 255, 255, 0.9);
--rac-arrow-color: #18181b;
--rac-arrow-size: 40px;
--rac-dot-color: rgba(0, 0, 0, 0.2);
--rac-dot-color-active: #18181b;
--rac-dot-size: 8px;
--rac-radius: 12px;
}Or skip the stylesheet entirely and target .rac-root, .rac-viewport, .rac-track, .rac-slide, .rac-arrow, .rac-dot with your own CSS/Tailwind.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode[] | — | One entry per slide |
| slidesPerView | number | 1 | Slides visible at once |
| slidesToScroll | number | 1 | Slides advanced per navigation |
| gap | number | 16 | Gap between slides (px) |
| loop | boolean | true | Wrap around at the ends |
| autoplay | number | — | Interval in ms; omit to disable |
| pauseOnHover | boolean | true | Pause autoplay on hover |
| pauseOnFocus | boolean | true | Pause autoplay on keyboard focus |
| draggable | boolean | true | Enable swipe/drag |
| animation | "slide" \| "fade" \| "zoom" \| "cube" \| "cards" | "slide" | Transition style (single-slide view) |
| duration | number | 0.5 | Transition duration (s), used when easing !== "spring" |
| easing | "linear" \| "easeIn" \| "easeOut" \| "easeInOut" \| "spring" | "spring" | Transition easing |
| direction | "ltr" \| "rtl" \| "ttb" \| "btt" | "ltr" | Reading/animation direction — ltr/rtl are horizontal, ttb/btt are vertical |
| height | number | 400 | Fixed pixel viewport height; required for vertical carousels with slidesPerView > 1 |
| showArrows | boolean | true | Show prev/next arrows |
| showDots | boolean | true | Show pagination dots |
| breakpoints | CarouselBreakpoint[] | [] | Responsive overrides |
| initialIndex | number | 0 | Starting index (uncontrolled) |
| activeIndex | number | — | Controlled index |
| onChange | (index: number) => void | — | Fires on index change |
| renderPrevArrow / renderNextArrow | (onClick, disabled) => ReactNode | — | Custom arrow rendering |
| renderDots | (props) => ReactNode | — | Custom dots rendering |
| ariaLabel | string | "Carousel" | Label for the carousel region |
| disableAnimation | boolean | false | Force-disable motion (auto-respects prefers-reduced-motion) |
Note:
animationstyles other than"slide"apply to single-item (slidesPerView={1}) carousels, where one slide is swapped for the next. Multi-item views (slidesPerView > 1) always use a sliding track, since fade/zoom/cube don't have a well-defined meaning across several simultaneously visible slides.
Accessibility
- Root has
role="region"+aria-roledescription="carousel"+aria-label - Each slide has
role="group"+aria-roledescription="slide"+ a numbered label - Left/Right arrow keys navigate horizontal carousels (respecting
direction); Up/Down arrow keys navigate vertical carousels (direction="ttb"/"btt"). Home/End jump to the first/last slide in either case - Arrows and dots are real
<button>elements witharia-label/aria-selected - Animations are skipped automatically when the OS
prefers-reduced-motionsetting is on
Development
npm install
npm run build # bundles ESM + CJS + .d.ts into dist/, copies styles.css
npm run dev # watch mode
npm run lint # tsc --noEmitPublishing
This is a scoped package (@elvitech/carousel). Scoped packages default to
private on npm, so the first publish must pass --access public
(subsequent publishes reuse the publishConfig.access: "public" already set
in package.json, so plain npm publish works after that):
npm login # once per machine, same account as @elvitech/vidply
npm run build
npm version patch # or minor / major — bumps version + git tag
npm publish --access publicBump version in package.json (or use npm version) before every publish —
npm rejects re-publishing an existing version.
License
MIT
