Logos Quote Testimonial
Original · freequote with a logo strip
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/tstx-logos-quote.json"use client";
import { useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
const ACCENTS = {
indigo: {
text: "text-indigo-600 dark:text-indigo-400",
ring: "ring-indigo-400/60 dark:ring-indigo-500/50",
chip: "bg-indigo-50 dark:bg-indigo-500/10",
avatar: "from-indigo-500 to-violet-500",
glow: "from-indigo-400/40 to-violet-400/20 dark:from-indigo-500/25 dark:to-violet-500/10",
},
emerald: {
text: "text-emerald-600 dark:text-emerald-400",
ring: "ring-emerald-400/60 dark:ring-emerald-500/50",
chip: "bg-emerald-50 dark:bg-emerald-500/10",
avatar: "from-emerald-500 to-sky-500",
glow: "from-emerald-400/40 to-sky-400/20 dark:from-emerald-500/25 dark:to-sky-500/10",
},
rose: {
text: "text-rose-600 dark:text-rose-400",
ring: "ring-rose-400/60 dark:ring-rose-500/50",
chip: "bg-rose-50 dark:bg-rose-500/10",
avatar: "from-rose-500 to-amber-500",
glow: "from-rose-400/40 to-amber-400/20 dark:from-rose-500/25 dark:to-amber-500/10",
},
amber: {
text: "text-amber-600 dark:text-amber-400",
ring: "ring-amber-400/60 dark:ring-amber-500/50",
chip: "bg-amber-50 dark:bg-amber-500/10",
avatar: "from-amber-500 to-rose-500",
glow: "from-amber-400/40 to-rose-400/20 dark:from-amber-500/25 dark:to-rose-500/10",
},
sky: {
text: "text-sky-600 dark:text-sky-400",
ring: "ring-sky-400/60 dark:ring-sky-500/50",
chip: "bg-sky-50 dark:bg-sky-500/10",
avatar: "from-sky-500 to-indigo-500",
glow: "from-sky-400/40 to-indigo-400/20 dark:from-sky-500/25 dark:to-indigo-500/10",
},
violet: {
text: "text-violet-600 dark:text-violet-400",
ring: "ring-violet-400/60 dark:ring-violet-500/50",
chip: "bg-violet-50 dark:bg-violet-500/10",
avatar: "from-violet-500 to-indigo-500",
glow: "from-violet-400/40 to-indigo-400/20 dark:from-violet-500/25 dark:to-indigo-500/10",
},
} as const;
type AccentKey = keyof typeof ACCENTS;
type Testimonial = {
id: string;
company: string;
accent: AccentKey;
mark: ReactNode;
quote: string;
author: string;
role: string;
metricValue: string;
metricLabel: string;
};
const TESTIMONIALS: Testimonial[] = [
{
id: "vertex",
company: "Vertex Labs",
accent: "indigo",
mark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinejoin="round" className="h-full w-full" aria-hidden="true">
<path d="M12 3 21 20H3z" />
<path d="M12 9 16.5 18h-9z" fill="currentColor" stroke="none" opacity="0.35" />
</svg>
),
quote:
"We cut our release cycle from three weeks to four days. Cadence gave every engineer one place to see what's shipping and why it actually matters to the roadmap.",
author: "Priya Nandakumar",
role: "VP of Engineering",
metricValue: "5x",
metricLabel: "Faster releases",
},
{
id: "halo",
company: "Halo Health",
accent: "emerald",
mark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" className="h-full w-full" aria-hidden="true">
<circle cx="12" cy="12" r="8.5" />
<path d="M12 8v8M8 12h8" />
</svg>
),
quote:
"Onboarding a new clinic used to take a full quarter of back-and-forth. Now it takes an afternoon, and nothing quietly falls through the cracks on the way.",
author: "Marcus Feld",
role: "Director of Operations",
metricValue: "92%",
metricLabel: "Less setup time",
},
{
id: "northpeak",
company: "Northpeak",
accent: "rose",
mark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinejoin="round" strokeLinecap="round" className="h-full w-full" aria-hidden="true">
<path d="M2 19 8.5 7l4 6 2.5-3.5L22 19z" />
</svg>
),
quote:
"Our support queue dropped by half in the first month. The team finally spends its hours on the hard problems instead of chasing down status updates all day.",
author: "Elena Sokolova",
role: "Head of Customer Success",
metricValue: "48%",
metricLabel: "Smaller support queue",
},
{
id: "orbit",
company: "Orbit Studio",
accent: "amber",
mark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="h-full w-full" aria-hidden="true">
<ellipse cx="12" cy="12" rx="9" ry="4.2" transform="rotate(-20 12 12)" />
<circle cx="12" cy="12" r="2.6" fill="currentColor" stroke="none" />
</svg>
),
quote:
"The forecasting alone paid for the whole contract. We caught a revenue dip six weeks before it would have ever landed on the books, and fixed it in time.",
author: "Daniel Osei",
role: "Finance Lead",
metricValue: "$1.2M",
metricLabel: "Revenue protected",
},
{
id: "lumen",
company: "Lumen Retail",
accent: "sky",
mark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" className="h-full w-full" aria-hidden="true">
<circle cx="12" cy="12" r="3.6" fill="currentColor" stroke="none" />
<path d="M12 3v2.5M12 18.5V21M3 12h2.5M18.5 12H21M5.6 5.6l1.8 1.8M16.6 16.6l1.8 1.8M18.4 5.6l-1.8 1.8M7.4 16.6l-1.8 1.8" />
</svg>
),
quote:
"Every store manager sees the same numbers now. No more spreadsheets emailed at midnight, no more arguing about whose version of the data is the real one.",
author: "Aiko Tanaka",
role: "Regional Director",
metricValue: "340",
metricLabel: "Stores aligned",
},
{
id: "fathom",
company: "Fathom",
accent: "violet",
mark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className="h-full w-full" aria-hidden="true">
<path d="M3 8q3-3 6 0t6 0 6 0" />
<path d="M3 13q3-3 6 0t6 0 6 0" />
<path d="M3 18q3-3 6 0t6 0 6 0" />
</svg>
),
quote:
"I've rolled out a lot of tools in my career. This is the first one the whole company actually kept using after week two, without anyone having to nag them.",
author: "Tomás Herrera",
role: "Chief of Staff",
metricValue: "96%",
metricLabel: "Team adoption",
},
];
function initials(name: string): string {
return name
.split(" ")
.map((part) => part[0] ?? "")
.slice(0, 2)
.join("")
.toUpperCase();
}
function Stars({ className }: { className: string }): ReactNode {
return (
<div className="flex gap-1" role="img" aria-label="Rated 5 out of 5 stars">
{[0, 1, 2, 3, 4].map((n) => (
<svg key={n} viewBox="0 0 20 20" fill="currentColor" className={`h-4 w-4 ${className}`} aria-hidden="true">
<path d="M10 1.6l2.6 5.3 5.8.9-4.2 4.1 1 5.8L10 15l-5.2 2.7 1-5.8L1.6 7.8l5.8-.9z" />
</svg>
))}
</div>
);
}
export default function TstxLogosQuote(): ReactNode {
const reduce = useReducedMotion();
const baseId = useId();
const panelId = `${baseId}-panel`;
const [activeIndex, setActiveIndex] = useState(0);
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
const active = TESTIMONIALS[activeIndex];
const accent = ACCENTS[active.accent];
const activeTabId = `${baseId}-tab-${activeIndex}`;
const onKey = (e: KeyboardEvent<HTMLButtonElement>, i: number): void => {
const len = TESTIMONIALS.length;
let next = i;
if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (i + 1) % len;
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (i - 1 + len) % len;
else if (e.key === "Home") next = 0;
else if (e.key === "End") next = len - 1;
else return;
e.preventDefault();
setActiveIndex(next);
tabRefs.current[next]?.focus();
};
return (
<section className="relative w-full overflow-hidden bg-white py-24 dark:bg-slate-950 sm:py-32">
<style>{`
@keyframes tlq-glow-pulse { 0%,100% { opacity:.5; transform:scale(1); } 50% { opacity:.9; transform:scale(1.09); } }
@keyframes tlq-float { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-6px); } }
.tlq-glow-a { animation: tlq-glow-pulse 7s ease-in-out infinite; }
.tlq-glow-b { animation: tlq-glow-pulse 9s ease-in-out infinite reverse; }
.tlq-float { animation: tlq-float 6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.tlq-glow-a, .tlq-glow-b, .tlq-float { animation: none !important; }
}
`}</style>
{/* Ambient background */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
<div className="tlq-glow-a absolute -top-24 left-1/4 h-72 w-72 rounded-full bg-gradient-to-br from-indigo-400/25 to-sky-400/15 blur-3xl dark:from-indigo-500/15 dark:to-sky-500/10" />
<div className="tlq-glow-b absolute -bottom-24 right-1/4 h-72 w-72 rounded-full bg-gradient-to-br from-violet-400/20 to-emerald-400/10 blur-3xl dark:from-violet-500/12 dark:to-emerald-500/8" />
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-slate-200 to-transparent dark:via-slate-800" />
</div>
<div className="relative mx-auto max-w-6xl px-6 lg:px-8">
{/* Header */}
<div className="mx-auto max-w-2xl text-center">
<span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/70 px-3.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-slate-600 backdrop-blur dark:border-slate-800 dark:bg-slate-900/70 dark:text-slate-300">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
Customer stories
</span>
<h2 className="mt-6 text-balance text-3xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
Teams don’t just try Cadence. They build their week around it.
</h2>
<p className="mt-4 text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-400">
Six teams, six very different problems, one platform they all rely on. Pick a company to read its story.
</p>
<div className="mt-6 flex items-center justify-center gap-3">
<Stars className="text-amber-500 dark:text-amber-400" />
<span className="text-sm font-medium text-slate-500 dark:text-slate-400">
4.9 / 5 · 800+ teams onboarded this year
</span>
</div>
</div>
{/* Featured quote */}
<div className="mx-auto mt-14 max-w-4xl">
<div
role="tabpanel"
id={panelId}
aria-labelledby={activeTabId}
tabIndex={0}
className="relative overflow-hidden rounded-3xl border border-slate-200/80 bg-white/80 p-8 shadow-xl shadow-slate-900/5 backdrop-blur-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-800 dark:bg-slate-900/60 dark:shadow-black/30 dark:focus-visible:ring-offset-slate-950 sm:p-12"
>
<div
aria-hidden="true"
className={`tlq-glow-a pointer-events-none absolute -right-16 -top-16 h-56 w-56 rounded-full bg-gradient-to-br ${accent.glow} blur-3xl transition-colors duration-500`}
/>
<AnimatePresence mode="wait">
<motion.div
key={active.id}
initial={reduce ? { opacity: 0 } : { opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
exit={reduce ? { opacity: 0 } : { opacity: 0, y: -14 }}
transition={{ duration: reduce ? 0.15 : 0.4, ease: "easeOut" }}
className="relative"
>
<div className="flex items-center justify-between gap-4">
<svg
viewBox="0 0 24 24"
fill="currentColor"
className={`tlq-float h-11 w-11 ${accent.text}`}
aria-hidden="true"
>
<path d="M7.5 6C5.6 6 4 7.6 4 9.5S5.6 13 7.5 13c.2 0 .3 0 .5-.1-.4 2-2 3.3-4 3.6V19c3.8-.4 6.5-3.4 6.5-7.6C10.5 7.9 9.2 6 7.5 6zm9 0C14.6 6 13 7.6 13 9.5S14.6 13 16.5 13c.2 0 .3 0 .5-.1-.4 2-2 3.3-4 3.6V19c3.8-.4 6.5-3.4 6.5-7.6C19.5 7.9 18.2 6 16.5 6z" />
</svg>
<Stars className={accent.text} />
</div>
<blockquote className="mt-6 text-pretty text-xl font-medium leading-relaxed text-slate-800 dark:text-slate-100 sm:text-2xl">
“{active.quote}”
</blockquote>
<div className="mt-8 flex flex-col gap-6 border-t border-slate-200 pt-6 dark:border-slate-800 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-4">
<span
className={`flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-gradient-to-br ${accent.avatar} text-sm font-semibold text-white shadow-inner`}
aria-hidden="true"
>
{initials(active.author)}
</span>
<div>
<div className="font-semibold text-slate-900 dark:text-white">{active.author}</div>
<div className="text-sm text-slate-500 dark:text-slate-400">
{active.role}, {active.company}
</div>
</div>
</div>
<div className="sm:text-right">
<div className={`text-2xl font-bold tracking-tight ${accent.text}`}>{active.metricValue}</div>
<div className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
{active.metricLabel}
</div>
</div>
</div>
</motion.div>
</AnimatePresence>
</div>
</div>
{/* Logo strip / tablist */}
<div className="mt-12">
<p className="text-center text-xs font-semibold uppercase tracking-widest text-slate-400 dark:text-slate-500">
Trusted by teams at
</p>
<div
role="tablist"
aria-label="Customer companies"
aria-orientation="horizontal"
className="mx-auto mt-6 grid max-w-4xl grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6"
>
{TESTIMONIALS.map((t, i) => {
const selected = i === activeIndex;
const a = ACCENTS[t.accent];
return (
<button
key={t.id}
ref={(el) => {
tabRefs.current[i] = el;
}}
type="button"
role="tab"
id={`${baseId}-tab-${i}`}
aria-selected={selected}
aria-controls={panelId}
tabIndex={selected ? 0 : -1}
onClick={() => setActiveIndex(i)}
onKeyDown={(e) => onKey(e, i)}
className={`flex items-center justify-center gap-2.5 rounded-xl border px-3 py-3.5 text-sm font-medium transition duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950 ${
selected
? `border-transparent ${a.chip} ${a.text} shadow-sm ring-2 ${a.ring}`
: "border-slate-200/70 bg-white/50 text-slate-500 opacity-70 hover:border-slate-300 hover:text-slate-700 hover:opacity-100 dark:border-slate-800 dark:bg-slate-900/40 dark:text-slate-400 dark:hover:border-slate-700 dark:hover:text-slate-200"
}`}
>
<span className="h-5 w-5 shrink-0">{t.mark}</span>
<span className="whitespace-nowrap">{t.company}</span>
</button>
);
})}
</div>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Three Card Testimonial Grid
OriginalA clean three-column grid of testimonial cards, each with a star rating, quote and an initial avatar with name and role.

Single Featured Quote
OriginalA large centred pull quote with a decorative quotation mark, five-star rating and a prominent author avatar for a single standout testimonial.

Logo Wall Marquee
OriginalA pair of infinitely scrolling, pause-on-hover marquees pairing a customer logo wall with compact quote cards, with reduced-motion support.

Masonry Testimonial Wall
OriginalA CSS-columns masonry wall of testimonial cards in varied lengths, each with a star rating and initial avatar for a natural, organic layout.

Aggregate Rating Spotlight
OriginalA split layout pairing a large aggregate rating score, star row and overlapping avatar stack with two featured review cards.

Auto-Rotating Quote Carousel
OriginalAn auto-advancing testimonial carousel that slides and crossfades between quotes with a live progress bar, pause-on-hover, and dot plus arrow navigation.

Staggered Reveal Testimonial Grid
OriginalA testimonial grid whose cards spring in with a staggered scroll reveal and light up with an animated gradient border on hover.

Infinite Marquee Testimonial Wall
OriginalA three-row marquee wall of review cards scrolling in alternating directions that pause on hover, with a shimmering gradient headline and faded edges.

Grid Testimonial
Originaltestimonial grid

Single Large Testimonial
Originalsingle large quote

Marquee Testimonial
Originaltestimonial marquee rows
Cards Avatar Testimonial
Originaltestimonial cards with avatars

