advanced-level-slider
v1.0.0
Published
A reusable React carousel slider built with Embla Carousel, supporting autoplay, continuous auto-scroll, custom cards, custom buttons, and flexible styling.
Maintainers
Readme
Advanced Level Slider
A reusable React / Next.js carousel package built on top of Embla Carousel.
advanced-level-slider gives you one flexible component:
CustomEmblaCarouselYou can use it for:
- Logo sliders
- Testimonials
- Card carousels
- Product sliders
- Autoplay sliders
- Continuous marquee-style sliders
- Custom next / previous buttons
- Custom dots or pagination controls
Installation
Install the package from npm:
npm install advanced-level-sliderThen import it:
import CustomEmblaCarousel from "advanced-level-slider";Your React or Next.js project only needs React installed, which is already included in normal React / Next.js apps.
Important Next.js App Router Note
This carousel uses React hooks and browser-based carousel behavior, so the component where you use it should be a Client Component.
Add this at the top of the file where you call CustomEmblaCarousel:
"use client";Example:
"use client";
import CustomEmblaCarousel from "advanced-level-slider";
export default function MySlider() {
return (
<CustomEmblaCarousel
items={[]}
CustomCard={() => null}
/>
);
}This is especially important in Next.js App Router because you pass functions like CustomCard and CustomButtonWrapper. Server Components cannot pass functions directly to Client Components.
Basic Usage
"use client";
import CustomEmblaCarousel from "advanced-level-slider";
const items = [
{ title: "Card 1", description: "First card description" },
{ title: "Card 2", description: "Second card description" },
{ title: "Card 3", description: "Third card description" },
];
export default function BasicSlider() {
return (
<CustomEmblaCarousel
items={items}
CustomCard={(item, index) => (
<div
key={index}
className="min-w-[300px] shrink-0 basis-[300px] px-4"
>
<div className="rounded-xl bg-white p-6 text-black shadow-md">
<h3 className="text-xl font-bold">{item.title}</h3>
<p>{item.description}</p>
</div>
</div>
)}
/>
);
}Logo Slider Example
Use isContinuousPlay for smooth marquee-style movement.
"use client";
import Image from "next/image";
import CustomEmblaCarousel from "advanced-level-slider";
const logos = [
{
name: "Logo 1",
image: "/logos/logo-1.svg",
width: 120,
height: 40,
},
{
name: "Logo 2",
image: "/logos/logo-2.svg",
width: 120,
height: 40,
},
{
name: "Logo 3",
image: "/logos/logo-3.svg",
width: 120,
height: 40,
},
];
export default function LogoSlider() {
return (
<CustomEmblaCarousel
items={logos}
isContinuousPlay
options={{
loop: true,
align: "start",
dragFree: true,
}}
continuousPlayConfig={{
speed: 0.8,
direction: "forward",
stopOnInteraction: false,
stopOnMouseEnter: true,
}}
wrapperClassName="!bg-cream"
CustomCard={(logo, index) => (
<div
key={`${logo.name}-${index}`}
className="flex min-w-[170px] shrink-0 basis-[170px] items-center justify-center px-6 sm:min-w-[200px] sm:basis-[200px] md:min-w-[230px] md:basis-[230px] lg:min-w-[235px] lg:basis-[235px] lg:px-10"
>
<div className="flex h-[46px] w-full items-center justify-center opacity-50 grayscale transition-all duration-300 hover:opacity-90">
<Image
src={logo.image}
alt={logo.name}
width={logo.width}
height={logo.height}
className="h-auto max-h-[32px] w-auto object-contain sm:max-h-[36px]"
/>
</div>
</div>
)}
/>
);
}Autoplay Slider Example
Use isAutoplay when you want the carousel to move slide-by-slide after a delay.
"use client";
import CustomEmblaCarousel from "advanced-level-slider";
const reviews = [
{ name: "Ali", text: "Amazing service." },
{ name: "Ahmed", text: "Very professional work." },
{ name: "Sara", text: "Loved the result." },
];
export default function ReviewSlider() {
return (
<CustomEmblaCarousel
items={reviews}
isAutoplay
autoplayConfig={{
delay: 3000,
stopOnInteraction: false,
stopOnMouseEnter: true,
}}
options={{
loop: true,
align: "center",
}}
CustomCard={(review, index) => (
<div
key={index}
className="min-w-[320px] shrink-0 basis-[320px] px-4"
>
<div className="rounded-2xl bg-white p-6 text-black shadow-lg">
<p className="mb-4">{review.text}</p>
<h4 className="font-semibold">{review.name}</h4>
</div>
</div>
)}
/>
);
}Custom Buttons Example
You can pass your own previous and next buttons using CustomButtonWrapper.
"use client";
import CustomEmblaCarousel from "advanced-level-slider";
const cards = [
{ title: "Slide 1" },
{ title: "Slide 2" },
{ title: "Slide 3" },
];
export default function SliderWithButtons() {
return (
<CustomEmblaCarousel
items={cards}
CustomButtonWrapper={(onPrev, onNext) => (
<div className="mb-6 flex justify-end gap-3">
<button
type="button"
onClick={onPrev}
className="rounded-full bg-black px-4 py-2 text-white"
>
Prev
</button>
<button
type="button"
onClick={onNext}
className="rounded-full bg-black px-4 py-2 text-white"
>
Next
</button>
</div>
)}
CustomCard={(item, index) => (
<div
key={index}
className="min-w-[300px] shrink-0 basis-[300px] px-4"
>
<div className="rounded-xl bg-white p-8 text-black shadow-md">
{item.title}
</div>
</div>
)}
/>
);
}Dots / Specific Slide Buttons Example
CustomButtonWrapper also gives you onSpecificChoosed, which lets you move to a specific slide by index.
"use client";
import CustomEmblaCarousel from "advanced-level-slider";
const slides = [
{ title: "Slide 1" },
{ title: "Slide 2" },
{ title: "Slide 3" },
];
export default function SliderWithDots() {
return (
<CustomEmblaCarousel
items={slides}
CustomButtonWrapper={(onPrev, onNext, onSpecificChoosed) => (
<div className="mb-5 flex items-center justify-center gap-2">
{slides.map((_, index) => (
<button
key={index}
type="button"
onClick={() => onSpecificChoosed(index)}
className="h-3 w-3 rounded-full bg-gray-400"
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
)}
CustomCard={(slide, index) => (
<div
key={index}
className="min-w-[300px] shrink-0 basis-[300px] px-4"
>
<div className="rounded-xl bg-white p-8 text-black shadow-md">
{slide.title}
</div>
</div>
)}
/>
);
}Props
items
items?: any[];Array of data rendered inside the carousel.
Example:
const items = [
{ title: "Card 1" },
{ title: "Card 2" },
];
<CustomEmblaCarousel items={items} />CustomCard
CustomCard?: (
item: any,
index: number,
firstActiveItemIndex: number
) => ReactNode;Controls how each slide/card looks.
| Argument | Meaning |
| --- | --- |
| item | Current item from the items array |
| index | Current item index |
| firstActiveItemIndex | Current selected Embla snap index |
Example:
CustomCard={(item, index, firstActiveItemIndex) => (
<div key={index}>
<h3>{item.title}</h3>
<p>Current active index: {firstActiveItemIndex}</p>
</div>
)}For continuous logo sliders, you usually do not need firstActiveItemIndex.
options
options?: EmblaOptionsType;Embla Carousel options.
Example:
options={{
loop: true,
align: "start",
dragFree: true,
}}Common options:
| Option | Meaning |
| --- | --- |
| loop | Enables infinite looping |
| align | Controls slide alignment: start, center, or end |
| dragFree | Enables smooth/free dragging |
| containScroll | Controls scroll containment |
| skipSnaps | Allows skipping snaps while dragging fast |
Default options used by the package:
{
loop: true,
align: "center"
}Your passed options override the defaults.
wrapperClassName
wrapperClassName?: string;Pass CSS or Tailwind classes to the Embla viewport wrapper.
Example:
wrapperClassName="bg-white py-10"With Tailwind important modifier:
wrapperClassName="!bg-cream"wrapperStyle
wrapperStyle?: CSSProperties;Pass inline styles to the carousel viewport.
Example:
wrapperStyle={{
paddingTop: "40px",
paddingBottom: "40px",
}}CustomButtonWrapper
CustomButtonWrapper?: (
onPrevClickChoosed: () => void,
onNextClickChoosed: () => void,
onSpecificChoosed: (n: number) => void
) => ReactNode;Used to render custom controls.
| Function | Meaning |
| --- | --- |
| onPrevClickChoosed | Go to previous slide |
| onNextClickChoosed | Go to next slide |
| onSpecificChoosed | Go to a specific slide by index |
Example:
CustomButtonWrapper={(onPrev, onNext, onSpecific) => (
<div>
<button onClick={onPrev}>Previous</button>
<button onClick={onNext}>Next</button>
<button onClick={() => onSpecific(2)}>Go to third slide</button>
</div>
)}isAutoplay
isAutoplay?: boolean;Set to true for normal slide-by-slide autoplay.
Example:
<CustomEmblaCarousel
items={items}
isAutoplay
/>Best for:
- Testimonials
- Hero sliders
- Case study sliders
- Product sliders
autoplayConfig
autoplayConfig?: {
delay?: number;
stopOnInteraction?: boolean;
stopOnMouseEnter?: boolean;
};Controls autoplay behavior.
Default values:
{
delay: 2500,
stopOnInteraction: false,
stopOnMouseEnter: true,
}Example:
autoplayConfig={{
delay: 4000,
stopOnInteraction: false,
stopOnMouseEnter: true,
}}| Option | Meaning |
| --- | --- |
| delay | Time between slides in milliseconds |
| stopOnInteraction | Stops autoplay when user interacts |
| stopOnMouseEnter | Pauses autoplay when mouse enters carousel |
isContinuousPlay
isContinuousPlay?: boolean;Set to true for marquee-style continuous movement.
Example:
<CustomEmblaCarousel
items={items}
isContinuousPlay
/>Best for:
- Logo sliders
- Brand rows
- Technology stack sliders
- Partner sliders
- Infinite smooth scrolling sections
If both isContinuousPlay and isAutoplay are true, continuous play is used first.
continuousPlayConfig
continuousPlayConfig?: {
speed?: number;
startDelay?: number;
direction?: "forward" | "backward";
stopOnInteraction?: boolean;
stopOnMouseEnter?: boolean;
};Controls continuous auto-scroll behavior.
Default values:
{
speed: 1.2,
startDelay: 0,
direction: "forward",
stopOnInteraction: false,
stopOnMouseEnter: true,
}Example:
continuousPlayConfig={{
speed: 0.8,
direction: "forward",
stopOnInteraction: false,
stopOnMouseEnter: true,
}}| Option | Meaning |
| --- | --- |
| speed | Movement speed |
| startDelay | Delay before movement starts |
| direction | forward or backward |
| stopOnInteraction | Stops scrolling when user interacts |
| stopOnMouseEnter | Pauses scrolling on hover |
Slide Width Rules
Embla works best when every direct slide item has width styles.
Good example:
CustomCard={(item, index) => (
<div
key={index}
className="min-w-[300px] shrink-0 basis-[300px] px-4"
>
<div className="rounded-xl bg-white p-6">
{item.title}
</div>
</div>
)}Important classes:
| Class | Purpose |
| --- | --- |
| min-w-[300px] | Minimum slide width |
| basis-[300px] | Flex basis / actual slide size |
| shrink-0 | Prevents slide from shrinking |
| px-4 | Adds spacing between slides |
Responsive example:
className="min-w-[260px] shrink-0 basis-[260px] sm:min-w-[320px] sm:basis-[320px] lg:min-w-[380px] lg:basis-[380px]"Best Use Cases
Logo slider
<CustomEmblaCarousel
items={logos}
isContinuousPlay
options={{ loop: true, align: "start", dragFree: true }}
/>Testimonials slider
<CustomEmblaCarousel
items={reviews}
isAutoplay
options={{ loop: true, align: "center" }}
/>Manual card slider
<CustomEmblaCarousel
items={items}
CustomCard={...}
/>Slider with buttons
<CustomEmblaCarousel
items={items}
CustomButtonWrapper={(onPrev, onNext) => (...)}
/>Slider with dots
<CustomEmblaCarousel
items={items}
CustomButtonWrapper={(onPrev, onNext, onSpecific) => (...)}
/>Troubleshooting
Error: Hooks can only be used in Client Components
Add this at the top of the component where you use the carousel:
"use client";Error: Functions cannot be passed directly to Client Components
This usually happens in Next.js App Router when you call the carousel from a Server Component.
Because CustomCard and CustomButtonWrapper are functions, the parent component should also be a Client Component:
"use client";Error: Module not found
First make sure the package is installed:
npm install advanced-level-sliderThen restart your dev server:
npm run devYou do not need to manually import or install Embla packages in the main project. Embla is installed as a dependency of this package.
Import is not working
Use this:
import CustomEmblaCarousel from "advanced-level-slider";Do not use this:
import CustomEmblaCarousel from "./advanced-level-slider";Slider items are squeezed or not moving properly
Make sure your CustomCard root element has width-related classes:
className="min-w-[300px] shrink-0 basis-[300px]"For logo slider:
className="min-w-[170px] shrink-0 basis-[170px]"Continuous slider is too fast
Lower the speed:
continuousPlayConfig={{
speed: 0.5,
}}Continuous slider is too slow
Increase the speed:
continuousPlayConfig={{
speed: 1.5,
}}Package Maintainer Notes
Before publishing, build the package:
npm run buildCheck package contents:
npm pack --dry-runExpected files:
README.md
dist/index.d.ts
dist/index.js
package.jsonPublish:
npm publishFor later updates, increase the version before publishing again:
npm version patch
npm run build
npm publishUse patch for fixes, minor for new features, and major for breaking changes.
Final Notes
- Use
isAutoplayfor slide-by-slide movement. - Use
isContinuousPlayfor smooth marquee-style movement. - Always give your slide root element
min-w,basis, andshrink-0classes. - In Next.js App Router, use
"use client";in the component where you call the carousel. - Import the package by name:
advanced-level-slider.
