How to Create a Carousel in NextJS using TailwindCSS and EmblaCarousel
Jasser Mark Arioste
Hello, hustlers! In this tutorial, you'll learn how to create a fully customizable carousel component in NextJS or React using TailwindCSS and the embla-carousel-react
package.
What is EmblaCarousel? #
EmblaCarousel is a lightweight (6.3kb min+gzipped), 100% open source, library agnostic carousel library with fluid motion and supports swipe gestures. It also does not have any dependencies on other libraries.
The embla-carousel-react
package is a simple React wrapper to the embla-carousel
library, so that we can easily access the states, events, and functions that it provides.
By combining it with React and TailwindCSS, it's very easy (and fast) to create your own carousel implementation.
Final Output #
This is the carousel that we'll be making in this tutorial:
Tutorial Objectives #
- Use NextJS as our react framework
- Install TailwindCSS to NextJS. How to Set Up NextJS and TailwindCSS in 2023
- Create a simple carousel component
- Add other components like
<Dots/>
, prev button, and next button components.
By the end of this tutorial, you'll learn how to create a carousel with any design imaginable. If the information provided in this tutorial is insufficient, you can always refer to the EmblaCarousel documentation
Step 0 - Project Setup #
To speed up the tutorial, let's create a new NextJS project with TailwindCSS pre-installed. If you are working on an existing NextJS project, please see my previous tutorial: How to Set up Nextjs and TailwindCSS.
Run the following command to bootstrap our project:
npx create-next-app -e https://github.com/jmarioste/next-tailwind-starter-2 next-carousel
Once everything is done, you can run the following commands to start the local dev server:
cd next-carousel
yarn dev
Step 1 - Installing Dependencies #
Let's install embla-carousel-react
and classnames
packages. The classnames package is always useful if you're using TailwindCSS since it's unavoidable to deal with toggling classes.
Use the following command to install both of these packages:
yarn add embla-carousel-react classnames
Step 2 - Creating a Simple Carousel #
Now that we're done with the setup, we can proceed with the implementation. First, create the file components/Carousel.tsx
.
// components/Carousel.tsx // import the hook and options type import useEmblaCarousel, { EmblaOptionsType } from "embla-carousel-react"; import { PropsWithChildren } from "react"; // Define the props type Props = PropsWithChildren & EmblaOptionsType; const Carousel = ({ children, ...options }: Props) => { // 1. useEmblaCarousel returns a emblaRef and we must attach the ref to a container. // EmblaCarousel will use that ref as basis for swipe and other functionality. const [emblaRef] = useEmblaCarousel(options); return ( // Attach ref to a div // 2. The wrapper div must have overflow:hidden <div className="overflow-hidden" ref={emblaRef}> {/* 3. The inner div must have a display:flex property */} {/* 4. We pass the children as-is so that the outside component can style it accordingly */} <div className="flex">{children}</div> </div> ); }; export default Carousel;
123456789101112131415161718192021222324
Usage
To use this component, we just pass the options and style each slide using the flex property. For example:
import type { NextPage } from "next"; import Carousel from "../components/Carousel"; import Image from "next/image"; const Home: NextPage = () => { const images = [ "https://placehold.co/480x300?font=roboto&text=Slide+1", "https://placehold.co/480x300?font=roboto&text=Slide+2", "https://placehold.co/480x300?font=roboto&text=Slide+3", "https://placehold.co/480x300?font=roboto&text=Slide+4", ]; return ( <div className="lg:w-3/4 mx-auto my-2"> <Carousel loop> {images.map((src, i) => { return ( // 👇 style each individual slide. // relative - needed since we use the fill prop from next/image component // h-64 - arbitrary height // flex[0_0_100%] // - shorthand for flex-grow:0; flex-shrink:0; flex-basis:100% // - we want this slide to not be able to grow or shrink and take up 100% width of the viewport. <div className="relative h-64 flex-[0_0_100%]" key={i}> {/* use object-cover + fill since we don't know the height and width of the parent */} <Image src={src} fill className="object-cover" alt="alt" /> </div> ); })} </Carousel> </div> ); }; export default Home;
123456789101112131415161718192021222324252627282930313233
NOTE: Don't forget to add domains in your next.config.js
:
/** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, swcMinify: true, images: { domains: ["placehold.co"], }, }; module.exports = nextConfig;
12345678910
The output should be a simple carousel that has swipe functionality like this:
If you only need a simple carousel like this, you're should be done at this point. In the next steps of this tutorial, we'll improve this carousel by adding the <Dots/>
and <CarouselControl/>
components.
Step 3 - Implementing the <Dots/>
Component
#
To implement the Dots component, we'll need access to the emblaApi
state. The emblaApi
is returned as the 2nd element from the useEmblaCarousel
hook. Let's make some changes to our Carousel component to make this happen:
// components/Carousel.tsx import useEmblaCarousel, { EmblaOptionsType } from "embla-carousel-react"; import { PropsWithChildren, useEffect, useState } from "react"; type Props = PropsWithChildren & EmblaOptionsType; const Carousel = ({ children, ...options }: Props) => { const [emblaRef, emblaApi] = useEmblaCarousel(options); // We need to track the selectedIndex to allow this component to re-render in react. // Since emblaRef is a ref, it won't re-render even if there are internal changes to its state. const [selectedIndex, setSelectedIndex] = useState(0); useEffect(() => { function selectHandler() { // selectedScrollSnap gives us the current selected index. const index = emblaApi?.selectedScrollSnap(); setSelectedIndex(index || 0); } emblaApi?.on("select", selectHandler); // cleanup return () => { emblaApi?.off("select", selectHandler); }; }, [emblaApi]); return ( <div className="overflow-hidden" ref={emblaRef}> <div className="flex">{children}</div> </div> ); }; export default Carousel;
12345678910111213141516171819202122232425262728293031323334
Now that we have access to the selectedIndex
, let's create the file components/Dots.tsx
.
import classNames from "classnames"; type Props = { itemsLength: number; selectedIndex: number; }; const Dots = ({ itemsLength, selectedIndex }: Props) => { const arr = new Array(itemsLength).fill(0); return ( <div className="flex gap-1 my-2 justify-center -translate-y-5"> {arr.map((_, index) => { const selected = index === selectedIndex; return ( <div className={classNames({ "h-2 w-2 rounded-full transition-all duration-300 bg-indigo-400": true, // tune down the opacity if slide is not selected "opacity-50": !selected, })} key={index} ></div> ); })} </div> ); }; export default Dots;
12345678910111213141516171819202122232425262728
In the above code, we're creating a dot for each slide, and styling the current slide to have a darker color than the rest.
Let's use this inside the Carousel component:
// ... const Carousel = ({ children, ...options }: Props) => { // ... const length = React.Children.count(children); return ( <> <div className="overflow-hidden" ref={emblaRef}> <div className="flex">{children}</div> </div> <Dots itemsLength={length} selectedIndex={selectedIndex} /> </> ); };
12345678910111213
After this step, you should have the following output:
Final Step - Adding the <CarouselControls/>
Component
#
In this step, we will add the next and previous buttons to our component. Fortunately, the emblaApi already provides us with some functions that we can use to simplify the implementation: canScrollPrev()
, canScrollNext()
, scrollPrev()
, and scrollNext()
.
First, create the file components/CarouselControls.tsx
:
// components/CarouselControls.tsx import classNames from "classnames"; type Props = { canScrollPrev: boolean; canScrollNext: boolean; onPrev(): void; onNext(): void; }; const CarouselControls = (props: Props) => { return ( <div className="flex justify-end gap-2 "> <button onClick={() => { if (props.canScrollPrev) { props.onPrev(); } }} disabled={!props.canScrollPrev} className={classNames({ "px-4 py-2 text-white rounded-md": true, "bg-indigo-200": !props.canScrollPrev, "bg-indigo-400": props.canScrollPrev, })} > Prev </button> <button onClick={() => { if (props.canScrollNext) { props.onNext(); } }} disabled={!props.canScrollNext} className={classNames({ "px-4 py-2 text-white rounded-md": true, "bg-indigo-200": !props.canScrollNext, "bg-indigo-400": props.canScrollNext, })} > Next </button> </div> ); }; export default CarouselControls;
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
In the above code, we render two buttons. We attach click handlers for both buttons. We also use the canScrollNext
, and canScrollPrev
states to style the buttons if it's disabled or not.
Next, let's use this in our <Carousel/>
component:
// components/Carousel.tsx // import the hook and options type import useEmblaCarousel, { EmblaOptionsType } from "embla-carousel-react"; import React from "react"; import { PropsWithChildren, useEffect, useState } from "react"; import CarouselControls from "./CarouselControls"; import Dots from "./Dots"; // ... const Carousel = ({ children, ...options }: Props) => { // ... const length = React.Children.count(children); const canScrollNext = !!emblaApi?.canScrollNext(); const canScrollPrev = !!emblaApi?.canScrollPrev(); return ( <> <div className="overflow-hidden" ref={emblaRef}> <div className="flex">{children}</div> </div> <Dots itemsLength={length} selectedIndex={selectedIndex} /> <CarouselControls canScrollNext={canScrollNext} canScrollPrev={canScrollPrev} onNext={() => emblaApi?.scrollNext()} onPrev={() => emblaApi?.scrollPrev()} /> </> ); }; export default Carousel;
123456789101112131415161718192021222324252627282930
That's it! After this step, you should have the same output as our final output.
Full Code and Demo #
The full code is available at Github: NextJS Carousel Tutorial
The demo is available at StackBlitz: NextJS Carousel Tutorial
Conclusion #
We learned how to implement a react carousel component using EmblaCarousel and we used TailwindCSS to customize it to our liking. EmblaCarousel is an amazing carousel library, so if you haven't tried it before, it's definitely worth checking out.
Resources #
Credits: Image by Harald Tedesco from Pixabay