# Carousel with overlay thumbnail navigation

> Carousel with overlay thumbnail navigation

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-carousel-10).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-carousel-10
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-carousel-10-light-ac2ed586cefef9f8.png
- Registry item: https://reui.io/r/new-york-v4/c-carousel-10.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-carousel-10.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `cn`
- Registry items: `carousel`

## Files

### `c-carousel-10.tsx`

```tsx
"use client"

import { useCallback, useEffect, useState } from "react"

import { cn } from "cn"
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  type CarouselApi,
} from "@/components/ui/carousel"

const ITEMS_COUNT = 10

export function Pattern() {
  const [mainApi, setMainApi] = useState<CarouselApi>()
  const [thumbApi, setThumbApi] = useState<CarouselApi>()
  const [selectedIndex, setSelectedIndex] = useState(0)

  const onThumbClick = useCallback(
    (index: number) => {
      if (!mainApi || !thumbApi) return
      mainApi.scrollTo(index)
    },
    [mainApi, thumbApi]
  )

  const onSelect = useCallback(() => {
    if (!mainApi || !thumbApi) return
    const index = mainApi.selectedScrollSnap()
    setSelectedIndex(index)
    thumbApi.scrollTo(index)
  }, [mainApi, thumbApi])

  useEffect(() => {
    if (!mainApi) return
    onSelect()
    mainApi.on("select", onSelect)
    mainApi.on("reInit", onSelect)
    return () => {
      mainApi.off("select", onSelect)
      mainApi.off("reInit", onSelect)
    }
  }, [mainApi, onSelect])

  return (
    <div className="flex w-full max-w-2xl items-center justify-center p-4">
      <div className="group relative w-full overflow-hidden rounded-xl">
        {/* Main Carousel */}
        <Carousel setApi={setMainApi} className="w-full">
          <CarouselContent>
            {Array.from({ length: ITEMS_COUNT }).map((_, index) => (
              <CarouselItem key={index}>
                <div className="bg-muted relative aspect-video w-full overflow-hidden">
                  <img
                    src={`https://picsum.photos/1200/675?grayscale&random=${index + 60}`}
                    alt={`Slide ${index + 1}`}
                    width={1200}
                    height={675}
                    className="h-full w-full object-cover"
                  />
                </div>
              </CarouselItem>
            ))}
          </CarouselContent>
        </Carousel>

        {/* Overlay Thumbnails Container */}
        <div className="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/60 to-transparent p-4 transition-opacity duration-300">
          <div className="relative mx-auto w-full max-w-md">
            <Carousel
              setApi={setThumbApi}
              opts={{
                containScroll: "keepSnaps",
                dragFree: true,
              }}
              className="w-full"
            >
              <CarouselContent className="-ml-2 flex-row">
                {Array.from({ length: ITEMS_COUNT }).map((_, index) => (
                  <CarouselItem
                    key={index}
                    className="basis-1/4 cursor-pointer pl-2 sm:basis-1/8"
                    onClick={() => onThumbClick(index)}
                  >
                    <div
                      className={cn(
                        "relative aspect-square overflow-hidden rounded-md border-2 transition-all duration-300",
                        index === selectedIndex
                          ? "border-white opacity-100 ring-2 ring-black/20"
                          : "border-white/40 opacity-50 hover:opacity-80"
                      )}
                    >
                      <img
                        src={`https://picsum.photos/200/200?grayscale&random=${index + 60}`}
                        alt={`Thumb ${index + 1}`}
                        width={200}
                        height={200}
                        className="h-full w-full object-cover"
                      />
                    </div>
                  </CarouselItem>
                ))}
              </CarouselContent>
            </Carousel>
          </div>
        </div>
      </div>
    </div>
  )
}
```
