All components

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.

Open in the catalogue

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

../../packages/blocks/src/testimonial-section/spotlight-testimonial.tsx
'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>
  )
}
./components/ui/timeline-animation.tsx
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>
  );
};

Similar components