Shadcnspace Builder
blocktestimonial-02source: ok · refreshed
Testimonial 02 - Testimonial Slider Showcase
A clean testimonial slider with customer photo, detailed quote, navigation controls, and partner logos—perfect for highlighting success stories and building instant trust on landing pages.
npx shadcn add "https://builder.shadcnspace.com/r/testimonial-02.json"free
Where it goes
testimonial
Visual style
minimal
Interaction
carousel
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumanimationmotion
Registry dependencies
badgecarousel
Files5 · source cached 10/2/2026, 1:26:41 AM
src/components/shadcn-space/blocks/testimonial-02/page.tsx
import Testimonial01 from "@/components/shadcn-space/blocks/testimonial-02";
export default function Page() {
return <Testimonial01 />;
}
src/components/shadcn-space/blocks/testimonial-02/testimonial.tsx
"use client";
import { useRef } from "react";
import { Badge } from "@/components/ui/badge";
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from "@/components/ui/carousel";
import { motion, useInView } from "motion/react";
export interface Testimonial {
quote: string;
author: string;
role: string;
image: string;
}
export interface Testimonial01Props {
badge?: string;
title?: string;
testimonials?: Testimonial[];
}
const defaultTestimonials: Testimonial[] = [
{
quote: "Shadcn Space replaced messy UI kits and half-baked templates. Today our dashboards look premium, scale beautifully, and our team focuses on real features instead of design debt.",
author: "Walter Dutcher",
role: "CEO",
image: "https://images.shadcnspace.com/assets/profiles/testimonial-user.png",
},
{
quote: "Shadcn Space replaced messy UI kits and half-baked templates. Today our dashboards look premium, scale beautifully, and our team focuses on real features instead of design debt.",
author: "Walter Dutcher",
role: "CEO",
image: "https://images.shadcnspace.com/assets/profiles/testimonial-user-2.png",
},
];
export default function Testimonial01({
testimonials = defaultTestimonials,
}: Testimonial01Props) {
const sectionRef = useRef<HTMLElement>(null);
const isInView = useInView(sectionRef, { once: true, amount: 0.1 });
return (
<section ref={sectionRef}>
<div className="max-w-7xl mx-auto sm:px-16 px-4 pt-12">
<div className="">
<motion.div
initial={{ opacity: 0, y: -40 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: -40 }}
transition={{ duration: 1, delay: 0.2, ease: "easeInOut" }}
className="flex flex-col gap-3"
>
<Badge className="text-sm h-auto py-1 px-3 border-0 w-fit">
Testimonials
</Badge>
<h2 className="sm:text-5xl text-xl leading-none font-medium tracking">
Success Stories
</h2>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 40 }}
transition={{ duration: 1, delay: 0.2, ease: "easeInOut" }}
className="pt-12 pb-8"
>
<Carousel>
<CarouselContent>
{testimonials.map((testimonial, index) => (
<CarouselItem key={index}>
<div className="grid grid-cols-12 gap-6 items-center">
<div className="lg:col-span-8 col-span-12 flex sm:flex-row flex-col sm:gap-10 gap-6 lg:pe-12">
<div className="shrink-0 flex items-start">
<img
src="https://images.shadcnspace.com/assets/svgs/icon-quote.svg"
alt="muted quote"
className="dark:hidden"
/>
<img
src="https://images.shadcnspace.com/assets/svgs/icon-quote-white.svg"
alt="muted quote"
className="hidden dark:block"
/>
</div>
<div className="flex flex-col gap-12">
<p className="sm:text-4xl text-xl text-muted-foreground">
{testimonial.quote}
</p>
<div>
<p className="text-base font-medium ">
{testimonial.author}
</p>
<p className="text-sm text-muted-foreground">
{testimonial.role}
</p>
</div>
</div>
</div>
<div className="md:col-span-4 col-span-12">
<div className="rounded-xl overflow-hidden">
<img
src={testimonial.image}
alt={testimonial.author}
width={500}
height={500}
className="w-full h-full object-cover"
/>
</div>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className={"-top-20 left-auto right-12"} />
<CarouselNext className={"-top-20 right-0"} />
</Carousel>
</motion.div>
</div>
</div>
</section>
);
}
src/components/shadcn-space/blocks/testimonial-02/index.tsx
import BrandSlider, { BrandList } from "@/components/shadcn-space/blocks/testimonial-02/brand-slider";
import Testimonial01Inner, { Testimonial } from "@/components/shadcn-space/blocks/testimonial-02/testimonial";
const brandList: BrandList[] = [
{
image: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-1.svg",
lightimg: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-white-1.svg",
name: "Brand 1",
},
{
image: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-2.svg",
lightimg: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-white-2.svg",
name: "Brand 2",
},
{
image: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-3.svg",
lightimg: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-white-3.svg",
name: "Brand 3",
},
{
image: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-4.svg",
lightimg: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-white-4.svg",
name: "Brand 4",
},
{
image: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-5.svg",
lightimg: "https://images.shadcnspace.com/assets/brand-logo/logoipsum-muted-white-5.svg",
name: "Brand 5",
},
];
const defaultTestimonials: Testimonial[] = [
{
quote: "Shadcn Space replaced messy UI kits and half-baked templates. Today our dashboards look premium, scale beautifully, and our team focuses on real features instead of design debt.",
author: "Walter Dutcher",
role: "CEO",
image: "https://images.shadcnspace.com/assets/profiles/testimonial-user.png",
},
{
quote: "Shadcn Space replaced messy UI kits and half-baked templates. Today our dashboards look premium, scale beautifully, and our team focuses on real features instead of design debt.",
author: "Errica Mcdowell",
role: "Marketing Head",
image: "https://images.shadcnspace.com/assets/profiles/testimonial-user-2.png",
},
];
export default function Testimonial01() {
return (
<main>
<div className="min-h-screen flex flex-col justify-center">
<Testimonial01Inner testimonials={defaultTestimonials} />
<BrandSlider brandList={brandList} />
</div>
</main>
);
}src/components/shadcn-space/blocks/testimonial-02/brand-slider.tsx
"use client";
import { useRef } from "react";
import { Marquee } from "@/components/shadcn-space/animations/marquee";
import { motion, useInView } from "motion/react";
export interface BrandList {
image: string;
name: string;
lightimg: string;
}
function BrandSlider({ brandList }: { brandList: BrandList[] }) {
const sectionRef = useRef<HTMLElement>(null);
const isInView = useInView(sectionRef, { once: true, amount: 0.1 });
return (
<section ref={sectionRef}>
<div className="py-6 md:py-10">
<div className="max-w-7xl mx-auto sm:px-16 px-4">
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 40 }}
transition={{ duration: 1, delay: 0.2, ease: "easeInOut" }}
className="flex flex-col gap-6 pt-12 border-t border-border"
>
<p className="text-base font-normal sm:px-2 px-10 text-muted-foreground text-center">
More than 12,000 businesses delight their customers with Shadcn Space
</p>
{brandList && brandList.length > 0 && (
<div className="py-4">
<Marquee pauseOnHover className="[--duration:20s] p-0">
{brandList.map((brand, index) => (
<div key={index}>
<img
src={brand.image}
alt={brand.name}
className="w-36 h-8 mr-6 lg:mr-20 dark:hidden"
/>
<img
src={brand.lightimg}
alt={brand.name}
className="hidden dark:block w-36 h-8 mr-12 lg:mr-20"
/>
</div>
))}
</Marquee>
</div>
)}
</motion.div>
</div>
</div>
</section>
);
}
export default BrandSlider;
src/components/shadcn-space/animations/marquee.tsx
"use client";
import { ComponentPropsWithoutRef } from "react";
import { cn } from "@/lib/utils";
interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
/**
* Optional CSS class name to apply custom styles
*/
className?: string;
/**
* Whether to reverse the animation direction
* @default false
*/
reverse?: boolean;
/**
* Whether to pause the animation on hover
* @default false
*/
pauseOnHover?: boolean;
/**
* Content to be displayed in the marquee
*/
children: React.ReactNode;
/**
* Whether to animate vertically instead of horizontally
* @default false
*/
vertical?: boolean;
/**
* Number of times to repeat the content
* @default 4
*/
repeat?: number;
}
export function Marquee({
className,
reverse = false,
pauseOnHover = false,
children,
vertical = false,
repeat = 4,
...props
}: MarqueeProps) {
return (
<>
<style>{`
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(calc(-100% - var(--gap))); }
}
@keyframes marquee-vertical {
from { transform: translateY(0); }
to { transform: translateY(calc(-100% - var(--gap))); }
}
.animate-marquee {
animation: marquee var(--duration) linear infinite;
}
.animate-marquee-vertical {
animation: marquee-vertical var(--duration) linear infinite;
}
`}</style>
<div
{...props}
className={cn(
"group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
{
"flex-row": !vertical,
"flex-col": vertical,
},
className
)}
>
{Array(repeat)
.fill(0)
.map((_, i) => (
<div
key={i}
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
"animate-marquee": !vertical,
"animate-marquee-vertical": vertical,
"group-hover:[animation-play-state:paused]": pauseOnHover,
"[animation-direction:reverse]": reverse,
})}
>
{children}
</div>
))}
</div>
</>
);
}
More from Shadcnspace Builder
About Us 01 - Impact MetricsblockAccordion-01 - DefaultcomponentAccordion-02 - Multi-levelcomponentAnimated Text-01 - Shiny TextcomponentAnimated Text-02 - Gradient TextcomponentAnimated Text-03 - TypingcomponentAnimated Text-04 - Rolling TextcomponentAnimated Text-05 - Shimmer Textcomponent
Browse all components in this library →