# V carousel 10

> Gallery with Thumbnails

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

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-carousel-10
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-carousel-10-light-cd1341c3d58225ae.png
- Registry item: https://ui.cnippet.dev/r/v-carousel-10.json
- Categories: carousel
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-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

- Registry items: `@cnippet/carousel`

## Files

### `registry/default/variants/v-carousel-10.tsx`

```tsx
"use client";

import useEmblaCarousel from "embla-carousel-react";
import * as React from "react";
import { cn } from "@/lib/utils";
import {
  Carousel,
  type CarouselApi,
  CarouselContent,
  CarouselItem,
} from "@/registry/default/ui/carousel";

const GALLERY = [
  { bg: "from-violet-400 to-purple-600", id: 1, label: "Mountain Sunset" },
  { bg: "from-sky-400 to-blue-600", id: 2, label: "Ocean Waves" },
  { bg: "from-emerald-400 to-teal-600", id: 3, label: "Forest Path" },
  { bg: "from-rose-400 to-pink-600", id: 4, label: "City Lights" },
  { bg: "from-amber-400 to-orange-500", id: 5, label: "Desert Dunes" },
  { bg: "from-indigo-400 to-cyan-500", id: 6, label: "Northern Lights" },
];

export default function Particle() {
  const [api, setApi] = React.useState<CarouselApi>();
  const [thumbRef, thumbApi] = useEmblaCarousel({
    containScroll: "keepSnaps",
    dragFree: true,
  });
  const [selected, setSelected] = React.useState(0);

  React.useEffect(() => {
    if (!api) return;
    const onSelect = () => {
      const idx = api.selectedScrollSnap();
      setSelected(idx);
      thumbApi?.scrollTo(idx);
    };
    api.on("select", onSelect);
    return () => {
      api.off("select", onSelect);
    };
  }, [api, thumbApi]);

  return (
    <div className="w-full max-w-sm space-y-2">
      <Carousel className="w-full" setApi={setApi}>
        <CarouselContent>
          {GALLERY.map((item) => (
            <CarouselItem key={item.id}>
              <div
                className={cn(
                  "relative flex h-52 flex-col items-center justify-end rounded-xl bg-linear-to-br pb-4 text-white",
                  item.bg,
                )}
              >
                <p className="font-medium text-sm drop-shadow">{item.label}</p>
              </div>
            </CarouselItem>
          ))}
        </CarouselContent>
      </Carousel>

      <div className="overflow-hidden" ref={thumbRef}>
        <div className="flex gap-2 px-px py-1">
          {GALLERY.map((item, i) => (
            <button
              aria-label={`View ${item.label}`}
              className={cn(
                "shrink-0 rounded-lg transition-all duration-200",
                selected === i
                  ? "ring-2 ring-primary ring-offset-2"
                  : "opacity-50 hover:opacity-80",
              )}
              key={item.id}
              onClick={() => api?.scrollTo(i)}
              type="button"
            >
              <div
                className={cn("h-12 w-16 rounded-lg bg-linear-to-br", item.bg)}
              />
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}
```
