Spotlight Testimonial
Spotlight Testimonial is a React and Tailwind CSS background component from UI Layouts, licensed MIT. Copy it and paste it into your project.
UI Layouts
MIT
backgrounds
What it is
Spotlight Testimonial is a React and Tailwind CSS background component from UI Layouts, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i motion
Licence
This component comes from UI Layouts and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Spotlight Testimonial
'use client'
import { cn } from '@/lib/utils'
import React, { useState, useEffect, useRef } from 'react'
import { motion, AnimatePresence } from 'motion/react'
import { TimelineAnimation } from '@/components/ui/timeline-animation'
const testimonials = [
{
id: 1,
quote: 'The level of craftsmanship is simply unparalleled. We saw a',
highlight: '40% increase',
quoteEnd: 'in developer productivity after integrating their UI kit.',
name: 'James Clear',
role: 'Head of Design at Atomic Systems',
url: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=880&auto=format&fit=crop',
},
{
id: 2,
quote:
'Working with this system has been transformative. Our team shipped features',
highlight: '3x faster',
quoteEnd: 'while maintaining exceptional quality standards.',
name: 'Sarah Chen',
role: 'Engineering Lead at Vercel',
url: 'https://images.unsplash.com/photo-1623366302587-b38b1ddaefd9?q=80&w=1025&auto=format&fit=crop',
},
{
id: 3,
quote:
"I've never experienced such a seamless developer experience. We reduced our build time by",
highlight: '60%',
quoteEnd: 'and our users immediately noticed the difference.',
name: 'Marcus Williams',
role: 'CTO at Linear',
url: 'https://images.unsplash.com/photo-1765438869321-d60141efd813?q=80&w=687&auto=format&fit=crop',
},
]
export const SpotlightTestimonial: React.FC = () => {
const [activeIndex, setActiveIndex] = useState(0)
const timelineRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const interval = setInterval(() => {
setActiveIndex((prev) => (prev + 1) % testimonials.length)
}, 3000)
return () => clearInterval(interval)
}, [])
const handleDotClick = (index: number) => {
setActiveIndex(index)
}
const activeTestimonial = testimonials[activeIndex]
return (
<section
ref={timelineRef}
className="bg-zinc-100 min-h-screen flex flex-col justify-center font-manrope"
>
{/* Header */}
<div className="max-w-7xl mx-auto px-6 mb-16 text-center">
<TimelineAnimation
animationNum={1}
timelineRef={timelineRef}
className="text-5xl font-bold tracking-tight text-zinc-900 mb-4"
>
Success Stories
</TimelineAnimation>
<TimelineAnimation
animationNum={2}
timelineRef={timelineRef}
className="text-lg text-zinc-600 max-w-3xl mx-auto"
>
Real results from real teams. Discover how industry leaders are
achieving remarkable outcomes with our design system.
</TimelineAnimation>
</div>
<div className="max-w-4xl mx-auto px-6 flex flex-col items-center text-center">
<TimelineAnimation
animationNum={3}
timelineRef={timelineRef}
className="size-16 rounded-full bg-zinc-200 flex items-center justify-center border border-zinc-300"
>
<svg
className="size-8 text-zinc-400"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M14.017 21L14.017 18C14.017 16.8954 14.9124 16 16.017 16H19.017V14H17.017C14.8079 14 13.017 12.2091 13.017 10V5H21.017V16L19.017 21H14.017ZM3.01709 21L3.01709 18C3.01709 16.8954 3.91252 16 5.01709 16H8.01709V14H6.01709C3.80795 14 2.01709 12.2091 2.01709 10V5H10.0171V16L8.01709 21H3.01709Z" />
</svg>
</TimelineAnimation>
<TimelineAnimation
animationNum={4}
timelineRef={timelineRef}
className="relative min-h-[200px] flex items-center justify-center mb-5"
>
<AnimatePresence mode="wait">
<motion.blockquote
key={activeTestimonial?.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.5, ease: [0.32, 0.72, 0, 1] }}
className="text-2xl sm:text-4xl font-semibold tracking-tight text-zinc-900 text-balance leading-tight"
>
"{activeTestimonial?.quote}{' '}
<span className="text-zinc-400 italic">
{activeTestimonial?.highlight}
</span>{' '}
{activeTestimonial?.quoteEnd}"
</motion.blockquote>
</AnimatePresence>
</TimelineAnimation>
<TimelineAnimation
animationNum={5}
timelineRef={timelineRef}
className="relative min-h-24 flex items-center"
>
<AnimatePresence mode="wait">
<motion.div
key={activeTestimonial?.id}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.4, ease: [0.32, 0.72, 0, 1] }}
className="flex flex-col items-center gap-2"
>
<img
src={activeTestimonial?.url}
alt={activeTestimonial?.name}
className="size-12 rounded-full bg-zinc-900 shadow-lg mb-2 object-cover"
/>
<p className="text-sm font-bold text-zinc-900 uppercase tracking-widest">
{activeTestimonial?.name}
</p>
<p className="text-xs text-zinc-500 font-medium">
{activeTestimonial?.role}
</p>
</motion.div>
</AnimatePresence>
</TimelineAnimation>
<TimelineAnimation
animationNum={6}
timelineRef={timelineRef}
className="mt-6 flex gap-1.5"
>
{testimonials.map((_, index) => (
<button
key={index}
onClick={() => handleDotClick(index)}
className={cn(
'h-2 rounded-full transition-all duration-300 cursor-pointer hover:bg-zinc-400',
index === activeIndex ? 'bg-zinc-900 w-8' : 'bg-zinc-300 w-2'
)}
aria-label={`Go to testimonial ${index + 1}`}
/>
))}
</TimelineAnimation>
</div>
</section>
)
}
import type { Variants } from 'motion/react';
import { type HTMLMotionProps, motion, useInView } from 'motion/react';
import type React from 'react';
type TimelineContentProps<T extends keyof HTMLElementTagNameMap> = {
children?: React.ReactNode;
animationNum: number;
className?: string;
timelineRef: React.RefObject<HTMLElement | null>;
as?: T;
customVariants?: Variants;
once?: boolean;
} & HTMLMotionProps<T>;
export const TimelineAnimation = <T extends keyof HTMLElementTagNameMap = 'div'>({
children,
animationNum,
timelineRef,
className,
as,
customVariants,
once = true,
...props
}: TimelineContentProps<T>) => {
const defaultSequenceVariants = {
visible: (i: number) => ({
filter: 'blur(0px)',
y: 0,
opacity: 1,
transition: {
delay: i * 0.5,
duration: 0.5,
},
}),
hidden: {
filter: 'blur(20px)',
y: 0,
opacity: 0,
},
};
const sequenceVariants = customVariants || defaultSequenceVariants;
const isInView = useInView(timelineRef, {
once,
});
const MotionComponent = motion[as || 'div'] as React.ElementType;
return (
<MotionComponent
initial='hidden'
animate={isInView ? 'visible' : 'hidden'}
custom={animationNum}
variants={sequenceVariants}
className={className}
{...props}
>
{children}
</MotionComponent>
);
};