# Testimonial 2

> testimonial

A shadcn/ui block from the [@cnippet-blocks](https://blocks.cnippet.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet-blocks/testimonial-2).

- Page, with a live preview: https://shadcn.exchange/r/cnippet-blocks/testimonial-2
- Screenshot: https://shadcn.exchange/shot/cnippet-blocks/testimonial-2/light.png
- Registry item: https://blocks.cnippet.dev/r/testimonial-2.json
- Categories: testimonial
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/cnippet-blocks/testimonial-2.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: `lucide-react`
- Registry items: `@cnippet/button`, `@cnippet/carousel`, `@cnippet/separator`

## Files

### `registry/default/sections/testimonial-2.tsx`

```tsx
"use client";

import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import Image from "next/image";
import React from "react";

import { Button } from "@/components/ui/button";
import {
  Carousel,
  type CarouselApi,
  CarouselContent,
  CarouselItem,
} from "@/components/ui/carousel";
import { Separator } from "@/components/ui/separator";

const stats = [
  {
    description: "in build and deploy",
    value: "3x Faster",
  },
  {
    description: "in page speeds",
    value: "56% increase",
  },
  {
    description: "of average time saved",
    value: "+4 year",
  },
];

const testimonials = [
  {
    avatar:
      "https://res.cloudinary.com/dvwtcsh5v/image/upload/v1770279333/a1.jpg",
    id: 1,
    name: "Marc Spector",
    quote:
      "How do you simplify complex operations for a growing team? Cnippet provided the tools to streamline everything effortlessly.",
    role: "Head of Customer Success",
  },
  {
    avatar:
      "https://res.cloudinary.com/dvwtcsh5v/image/upload/v1770279333/a2.jpg",
    id: 2,
    name: "Angela Moss",
    quote:
      "How do we scale efficiently without losing quality? Beam delivered the infrastructure to grow confidently and sustainably.",
    role: "VP of Engineering",
  },
  {
    avatar:
      "https://res.cloudinary.com/dvwtcsh5v/image/upload/v1770279333/a3.jpg",
    id: 3,
    name: "Alex Rivera",
    quote:
      "Can we achieve faster time-to-market while maintaining security? Cnippet provided the framework to innovate without compromise.",
    role: "Product Manager",
  },
  {
    avatar:
      "https://res.cloudinary.com/dvwtcsh5v/image/upload/v1770279333/a4.jpg",
    id: 4,
    name: "Riya Patel",
    quote:
      "How can we create seamless experiences across all touchpoints? Cnippet provided the design system to unify our entire product.",
    role: "Design Director",
  },
  {
    avatar:
      "https://res.cloudinary.com/dvwtcsh5v/image/upload/v1770279333/a5.jpg",
    id: 5,
    name: "David Kim",
    quote:
      "How do we maintain uptime while shipping continuously? Cnippet provided the reliability tools to deploy with confidence.",
    role: "Engineering Lead",
  },
];

export default function Component() {
  const [api, setApi] = React.useState<CarouselApi | null>(null);
  const [_current, setCurrent] = React.useState(0);

  React.useEffect(() => {
    if (!api) return;

    setCurrent(api.selectedScrollSnap());

    const onSelect = () => {
      setCurrent(api.selectedScrollSnap());
    };

    api.on("select", onSelect);

    return () => {
      api.off("select", onSelect);
    };
  }, [api]);

  const scrollPrev = React.useCallback(() => {
    api?.scrollPrev();
  }, [api]);

  const scrollNext = React.useCallback(() => {
    api?.scrollNext();
  }, [api]);

  return (
    <section className="w-full bg-white py-12 md:py-24 dark:bg-background">
      <div className="mx-auto max-w-4xl px-6 lg:px-0">
        <div className="grid grid-cols-1 sm:grid-cols-12">
          <div className="relative sm:col-span-10">
            <div className="absolute right-0 z-20 h-full w-10 bg-linear-to-l from-white dark:from-background" />
            <Carousel
              className="w-full"
              opts={{ align: "start", loop: true }}
              setApi={setApi}
            >
              <CarouselContent>
                {testimonials.map((testimonial) => (
                  <CarouselItem key={testimonial.id}>
                    <div className="space-y-12">
                      <h2 className="relative max-w-4xl font-normal text-4xl text-neutral-900 tracking-tight sm:text-4xl dark:text-neutral-100">
                        <svg
                          className="absolute top-1 left-0 -z-10 w-24 max-w-full text-neutral-200 dark:text-neutral-700"
                          fill="currentColor"
                          height="86"
                          viewBox="0 0 108 86"
                          width="108"
                          xmlns="http://www.w3.org/2000/svg"
                        >
                          <path
                            d="M38.8484 16.236L15 43.5793L78.2688 15L18.1218 71L93 34.1172L70.2047 65.2739"
                            stroke="currentColor"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                            strokeWidth="28"
                          />
                        </svg>
                        {testimonial.quote.split("Cnippet provided").length >
                        1 ? (
                          <>
                            {testimonial.quote.split("Cnippet provided")[0]}
                            <span className="text-gray-400">
                              Cnippet provided
                            </span>
                            {testimonial.quote
                              .split("Cnippet provided")[1]
                              .split("streamline").length > 1 ? (
                              <>
                                {
                                  testimonial.quote
                                    .split("Cnippet provided")[1]
                                    .split("streamline")[0]
                                }
                                <span className="text-gray-400">
                                  streamline
                                </span>
                                {
                                  testimonial.quote
                                    .split("Cnippet provided")[1]
                                    .split("streamline")[1]
                                }
                              </>
                            ) : (
                              testimonial.quote.split("Cnippet provided")[1]
                            )}
                          </>
                        ) : (
                          testimonial.quote
                        )}
                      </h2>
                      <div className="flex items-center gap-4">
                        <Image
                          alt={testimonial.name}
                          className="size-14 rounded-full object-cover"
                          height={400}
                          src={testimonial.avatar}
                          width={400}
                        />
                        <div>
                          <p className="font-semibold text-gray-900 text-lg dark:text-neutral-100">
                            {testimonial.name}
                          </p>
                          <p className="text-gray-600 text-sm dark:text-neutral-400">
                            {testimonial.role}
                          </p>
                        </div>
                      </div>
                    </div>
                  </CarouselItem>
                ))}
              </CarouselContent>
            </Carousel>
          </div>
          <div className="flex items-end justify-end sm:col-span-2">
            <div className="flex items-center gap-2">
              <Button
                className="size-10 rounded-lg border border-blue-600 bg-blue-700 p-0 text-gray-900 hover:bg-blue-600"
                onClick={scrollPrev}
                size="icon"
                variant="ghost"
              >
                <ChevronLeftIcon className="size-5 text-white" />
              </Button>
              <Button
                className="size-10 rounded-lg border border-blue-600 bg-blue-700 p-0 text-gray-900 hover:bg-blue-600"
                onClick={scrollNext}
                size="icon"
                variant="ghost"
              >
                <ChevronRightIcon className="size-5 text-white" />
              </Button>
            </div>
          </div>
        </div>

        <Separator className="my-12" />
        <div className="grid grid-cols-1 gap-6 sm:grid-cols-3">
          {stats.map((stat) => (
            <div
              className="rounded-2xl bg-gray-100 p-8 text-center dark:bg-neutral-800"
              key={stat.value}
            >
              <h3 className="mb-3 font-semibold text-4xl text-gray-900 sm:text-4xl! dark:text-neutral-100">
                {stat.value}
              </h3>
              <p className="text-gray-600 text-sm leading-relaxed dark:text-neutral-400">
                {stat.description}
              </p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}
```
