React Carousel Component Examples
Drop-in React Carousel examples: basic slider, arrows, built-in pagination dots, loop, multiple slides per view, and vertical orientation. Built on Embla.
Carousels show up across product UIs — image galleries, onboarding flows, testimonial sliders, content cards on a homepage, product tours. The touch-and-drag behavior is the part most teams do not want to re-implement, which is why almost every shipped React carousel ends up wrapping embla-carousel-react. The Carousel component in Drivn is a thin styling layer over that engine, exposing a dot-notation API — Carousel.Content, Carousel.Item, Carousel.Previous, Carousel.Next, Carousel.Dots — so a basic slider is one import.
This page collects the patterns that come up in production. Each snippet is copy-paste ready and stays consistent with the Carousel source the Drivn CLI writes into your repo. The default render is horizontal with scroll-snap, full-width items, and keyboard arrow nav already wired on the root via a handleKeyDown callback. Flip to vertical, render multiple slides per view, or enable infinite loop by changing one prop on the root or one className on the items.
If you need autoplay or any other Embla plugin, the plugins prop forwards directly to useEmblaCarousel. The examples below stay focused on the most common shapes — basic, with arrows, with dots, looping, multiple per view, and vertical — so you can pick the one closest to your case and rename a few classes. For external API access (driving the carousel from a parent component), pass a setApi callback and store the returned CarouselApi.
Pagination dots
Carousel.Dots renders a row of clickable dots underneath the slides — one per snap point, with the active dot highlighted via the bg-foreground class. Drop the tag inside the carousel root and Drivn reads the snap list from context, marks the active dot via selectedIndex === i, and calls api.scrollTo(i) on click. No useState, no useEffect, no manual scrollSnapList() plumbing — the boilerplate is in the component, not your page.
The dots can stand alone or sit alongside the prev/next arrows. Combine them when the carousel has more than three or four slides so users have both a "swipe-style" anchor (the dots) and a "click-to-step" control (the arrows). For a single-image hero where dots are visual noise, drop the Carousel.Dots tag and keep the arrows.
1 <Carousel> 2 <Carousel.Content> 3 <Carousel.Item>Slide 1</Carousel.Item> 4 <Carousel.Item>Slide 2</Carousel.Item> 5 <Carousel.Item>Slide 3</Carousel.Item> 6 </Carousel.Content> 7 <Carousel.Dots /> 8 </Carousel>
Infinite loop
Pass opts={{ loop: true }} on the Carousel root and Embla wraps around — scrolling past the last slide returns to the first, and canScrollPrev stays true at the start. Loop mode is the right default for testimonial sliders, autoplay banners, and any carousel where the slide order is not chronological. For step-by-step onboarding flows, leave loop off so the prev arrow disables on the first slide and the next arrow disables on the last.
The opts prop accepts every option from EmblaOptionsType — alignment (align: 'start' | 'center' | 'end'), drag-free mode, slide-skip behavior, and so on. The full option list lives in the Embla docs; Drivn forwards the object verbatim to useEmblaCarousel without any transformation.
1 <Carousel opts={{ loop: true }}> 2 <Carousel.Content> 3 <Carousel.Item>Slide 1</Carousel.Item> 4 <Carousel.Item>Slide 2</Carousel.Item> 5 <Carousel.Item>Slide 3</Carousel.Item> 6 </Carousel.Content> 7 <Carousel.Previous /> 8 <Carousel.Next /> 9 </Carousel>
Multiple slides per view
By default each Carousel.Item has basis-full, so one slide fills the viewport. Override the basis class on each item — basis-1/3 for three per view, basis-1/2 for two, basis-1/4 for four — and the carousel renders multiple slides at once with the rest queued for scroll. Pair it with opts={{ align: 'start' }} so the leftmost visible slide aligns to the left edge of the viewport rather than centering.
This is the right pattern for product card rows, image grids that scroll horizontally, or category tile carousels. The combination of basis-1/3 and align: 'start' is the most common — three cards visible, the user can drag or click to reveal the next set. For responsive layouts, swap basis classes via Tailwind breakpoints: basis-full md:basis-1/2 lg:basis-1/3.
1 <Carousel opts={{ align: "start" }}> 2 <Carousel.Content> 3 <Carousel.Item className="basis-1/3"> 4 Slide 1 5 </Carousel.Item> 6 <Carousel.Item className="basis-1/3"> 7 Slide 2 8 </Carousel.Item> 9 <Carousel.Item className="basis-1/3"> 10 Slide 3 11 </Carousel.Item> 12 <Carousel.Item className="basis-1/3"> 13 Slide 4 14 </Carousel.Item> 15 <Carousel.Item className="basis-1/3"> 16 Slide 5 17 </Carousel.Item> 18 </Carousel.Content> 19 <Carousel.Previous /> 20 <Carousel.Next /> 21 </Carousel>
Vertical orientation
Set orientation="vertical" on the root and the carousel scrolls top-to-bottom instead of left-to-right. The Carousel source flips the Embla axis to y, swaps the container layout to flex-col, and changes the item spacing class from pl-4 to pt-4. The Carousel.Content needs an explicit height — h-[300px] works for most cases — because vertical scroll needs a bounded container.
Vertical carousels are less common but show up in mobile-first feeds, sidebar navigation, and timeline-style content. The arrow buttons still work but their default position is tuned for horizontal layouts; override className on Carousel.Previous and Carousel.Next if you want them above and below the slides instead of left and right.
1 <Carousel orientation="vertical"> 2 <Carousel.Content className="h-[300px]"> 3 <Carousel.Item>Slide 1</Carousel.Item> 4 <Carousel.Item>Slide 2</Carousel.Item> 5 <Carousel.Item>Slide 3</Carousel.Item> 6 </Carousel.Content> 7 </Carousel>
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
No. The Carousel source imports useEmblaCarousel from embla-carousel-react and two chevron icons from lucide-react. The prev/next buttons reuse the existing Button component. No Radix, no cva, no floating-ui — the slider is Embla, the arrows are styled Button instances, and the layout is Tailwind.
Yes. Pass a setApi callback prop and Drivn calls it with the Embla API once the carousel mounts. Store the API in useState<CarouselApi>() and you can call api.scrollTo(2), api.scrollNext(), or any other Embla method from a parent button, a sibling component, or an effect that responds to URL changes.
Install embla-carousel-autoplay and pass it via the plugins prop on the Carousel root. The plugin accepts options like delay, stopOnInteraction, and stopOnMouseEnter. The contract is owned by Embla so the same plugin works in shadcn, Drivn, or any other Embla wrapper without modification — Drivn just forwards the array.
Because the most common slider is one slide per view, and basis-full paired with min-w-0 shrink-0 grow-0 makes each slide fill the viewport regardless of content size. Override basis-full with basis-1/3 or any other Tailwind basis class on individual items to render multiple slides per view. The default exists so the simplest case requires zero className overrides.
The Carousel file starts with "use client" because Embla uses useState, useEffect, and event handlers. You can import it from a Server Component, but it renders on the client. If your page is a Server Component, leave the import where it is — Next.js draws the client boundary correctly based on the use client directive at the top of the file.