Embla Carousel is a lightweight, customizable slider library that works seamlessly with React and Next.js. Whether you're building a product showcase, image gallery, or interactive section, Embla provides full control with a clean, low-level API. In this tutorial, we'll walk through a step-by-step setup of Embla Carousel in a Next.js 15 project — starting with a basic setup and gradually adding features like separated slide components, arrow controls, and dot navigation.
Install Embla Carousel Library
npm install embla-carousel-react
Setup 1: Basic
This is the most minimal Embla Carousel setup. We use useEmblaCarousel() to create a scrollable container. The emblaRef is attached to the wrapper element, and basic slides are placed inside. This is a great starting point to confirm that Embla is working correctly in your environment.
To make the code cleaner and more maintainable, we move each slide into its own reusable <Slide /> component. We also prepare slide data as an array of objects (slideInfo) so it's easier to map over and extend. This is a common pattern in real-world carousels that display content like cities, products, or blog posts.
In this step, we access the emblaApi returned by useEmblaCarousel() to enable previous/next navigation. Two buttons (< and >) call scrollPrev() and scrollNext() to move between slides programmatically. This gives users more control and improves UX on non-touch devices.
Note: We can use the same Slide component made in Setup 2
Here we add dot-based pagination. Using emblaApi.selectedScrollSnap(), we track the currently visible slide and update selectedIndex in React state. Each dot is a button that calls emblaApi.scrollTo(index) to jump directly to a slide. This setup gives your users both swipe and click-based navigation — ideal for carousels with visual indicators.
Note: We are using the same Slide component created during CarouselB Setup
To preview all carousel setups side by side, we include each variation inside the home page. This makes it easier to compare the design and behavior of each version as you scroll.
app/page.tsx
import Carousel from "./components/Carousel";import CarouselB from "./components/CarouselB";import CarouselC from "./components/CarouselC";import CarouselD from "./components/CarouselD";export default function Home() { return ( <div className="flex flex-col gap-[40px] w-full max-w-[1280px] mx-auto py-[40px]"> <Carousel /> <CarouselB /> <CarouselC /> <CarouselD /> </div> );}