Testimonials
Customer quotations with author identities.
Give your AI assistant this page as context.
Connect your library with MCPINTERACTIVE PREVIEW · SAMPLE DATA
Sample testimonial layout
This is illustrative placeholder copy. Replace it with a real customer quote before publishing.
Sample customer
Use this space to show what your customers value about your product.
Sample customer
blocks/testimonialsUsage
Get this block from a saved library export or the connected CLI. The imports below refer to local files installed in your app.
Import styles/components.css, then styles/blocks.css, then styles/theme.css once. Wrap your app in ThemeScope from components/ui/utils so typography, focus and overlays share the theme. The installation guide covers runtime dependencies and React/Next.js setup.
This import is the entry point, not a complete integration example. Supply the required props and connect callbacks to your application. The full ZIP includes examples/blocks-preview.tsx with the populated preview and local sample callbacks; adapt those examples to your product.
import { TestimonialsBlock } from './components/blocks/marketing';
// See Source and props below for the complete typed interface.Design system
This block consumes your semantic colors, spacing, typography and shape tokens. Import the compiled styles and your theme once as described in Export.
Icons use Lucide React. Install lucide-react@1.47.0 alongside React and Base UI.
Dependencies: Accordion, Avatar, Badge, Card.
Behavior and accessibility
- Provide meaningful labels for interactive controls.
- Connect action callbacks to your application logic.
- Verify keyboard navigation and contrast in your application.
Status: experimental during early access. Sample previews do not send emails, process payments or upload files to a service.
Source and props
This is the exported source, using the same implementation as the editor. The complete source group is included below; related blocks share it to avoid duplicating behavior.
"use client";
import type { ReactNode } from "react";
import {
ArrowRight,
ArrowUpRight,
Check,
Layers3,
Quote,
SlidersHorizontal,
Workflow,
} from "lucide-react";
import { Accordion, Avatar, Badge, Card } from '../ui';
function MarketingHeading({
title,
description,
eyebrow,
}: {
title: string;
description?: string;
eyebrow?: string;
}) {
return (
<header className="cr-marketing-heading">
<div>
{eyebrow && <p className="cr-marketing-eyebrow">{eyebrow}</p>}
<h2>{title}</h2>
</div>
{description && <p className="cr-description">{description}</p>}
</header>
);
}
export function HeroBlock({
eyebrow,
title,
description,
primary,
secondary,
children,
}: {
eyebrow: string;
title: string;
description: string;
primary: { label: string; href: string };
secondary?: { label: string; href: string };
children?: ReactNode;
}) {
return (
<section className="cr-hero cr-marketing-hero">
<div className="cr-hero-decoration" aria-hidden="true">
<span>
<Layers3 strokeWidth={1.75} />
</span>
<span>
<SlidersHorizontal strokeWidth={1.75} />
</span>
<span>
<Workflow strokeWidth={1.75} />
</span>
</div>
<div className="cr-hero-content">
<Badge variant="outline">
{eyebrow}
<ArrowUpRight size={13} strokeWidth={1.75} aria-hidden="true" />
</Badge>
<h1>{title}</h1>
<p className="cr-description">{description}</p>
<div className="cr-hero-actions">
<a className="cr-button" href={primary.href}>
{primary.label}
<ArrowRight size={17} strokeWidth={1.75} aria-hidden="true" />
</a>
{secondary && (
<a className="cr-button" data-variant="ghost" href={secondary.href}>
{secondary.label}
<ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" />
</a>
)}
</div>
</div>
{children && <div className="cr-hero-media">{children}</div>}
</section>
);
}
export function FeaturesBlock({
title,
description,
eyebrow,
items,
}: {
title: string;
description: string;
eyebrow?: string;
items: Array<{
title: string;
description: string;
icon?: ReactNode;
visual?: ReactNode;
eyebrow?: string;
href?: string;
linkLabel?: string;
}>;
}) {
const fallbackIcons = [Layers3, Workflow, SlidersHorizontal];
return (
<section className="cr-marketing-section cr-features-section">
<MarketingHeading
title={title}
description={description}
eyebrow={eyebrow}
/>
<div className="cr-features-grid">
{items.map((item, index) => {
const Icon = fallbackIcons[index % fallbackIcons.length];
return (
<Card
key={item.title}
className="cr-feature-card"
data-featured={(index === 0 && items.length >= 3) || undefined}
data-has-visual={Boolean(item.visual) || undefined}
>
<div className="cr-feature-copy">
<div className="cr-feature-topline">
<span aria-hidden="true" className="cr-feature-icon">
{item.icon ?? <Icon size={22} strokeWidth={1.75} />}
</span>
<span className="cr-feature-index" aria-hidden="true">
{String(index + 1).padStart(2, "0")}
</span>
</div>
{item.eyebrow && (
<p className="cr-marketing-eyebrow">{item.eyebrow}</p>
)}
<h3>{item.title}</h3>
<p className="cr-description">{item.description}</p>
{item.href && (
<a className="cr-feature-link" href={item.href}>
{item.linkLabel ?? "Explore feature"}
<ArrowUpRight
size={16}
strokeWidth={1.75}
aria-hidden="true"
/>
</a>
)}
</div>
{item.visual && (
<div className="cr-feature-visual">{item.visual}</div>
)}
</Card>
);
})}
</div>
</section>
);
}
export function PricingBlock({
title,
plans,
eyebrow,
description,
}: {
title: string;
eyebrow?: string;
description?: string;
plans: Array<{
name: string;
price: string;
period: string;
description: string;
features: string[];
href: string;
action: string;
featured?: boolean;
}>;
}) {
return (
<section className="cr-marketing-section cr-pricing-section">
<MarketingHeading
title={title}
description={description}
eyebrow={eyebrow}
/>
<div className="cr-pricing-grid">
{plans.map((plan) => (
<Card
key={plan.name}
className="cr-pricing-card"
data-featured={plan.featured || undefined}
>
<div className="cr-plan-heading">
<h3>{plan.name}</h3>
<div
className="cr-price-recommendation"
aria-hidden={!plan.featured || undefined}
>
{plan.featured && <Badge variant="primary">Recommended</Badge>}
</div>
</div>
<p className="cr-plan-description cr-description">
{plan.description}
</p>
<p className="cr-price">
{plan.price}
<small>{plan.period}</small>
</p>
<ul className="cr-price-features">
{plan.features.map((feature) => (
<li key={feature}>
<Check size={17} strokeWidth={1.75} aria-hidden="true" />
<span>{feature}</span>
</li>
))}
</ul>
<a
className="cr-button"
data-variant={plan.featured ? "primary" : "outline"}
href={plan.href}
>
{plan.action}
<ArrowRight size={16} strokeWidth={1.75} aria-hidden="true" />
</a>
</Card>
))}
</div>
</section>
);
}
export function TestimonialsBlock({
title,
quotes,
eyebrow,
description,
}: {
title: string;
eyebrow?: string;
description?: string;
quotes: Array<{ quote: string; name: string; role: string; avatar?: string }>;
}) {
return (
<section className="cr-marketing-section cr-testimonials-section">
<MarketingHeading
title={title}
description={description}
eyebrow={eyebrow}
/>
<div className="cr-testimonials-grid">
{quotes.map((quote, index) => (
<Card
key={quote.name}
className="cr-testimonial-card"
data-featured={index === 0 || undefined}
>
<figure className="cr-testimonial-figure">
<Quote
className="cr-quote-icon"
size={26}
strokeWidth={1.75}
aria-hidden="true"
/>
<blockquote className="cr-quote">
<p>{quote.quote}</p>
</blockquote>
<figcaption className="cr-quote-author">
<Avatar name={quote.name} src={quote.avatar} size={40} />
<div>
<cite>{quote.name}</cite>
<p className="cr-description">{quote.role}</p>
</div>
</figcaption>
</figure>
</Card>
))}
</div>
</section>
);
}
export function FaqBlock({
title,
items,
eyebrow,
description,
contact,
}: {
title: string;
eyebrow?: string;
description?: string;
contact?: { label: string; href: string };
items: Array<{ question: string; answer: ReactNode }>;
}) {
return (
<section className="cr-marketing-section cr-faq-section">
<div className="cr-faq-layout">
<div className="cr-faq-intro">
<MarketingHeading
title={title}
description={description}
eyebrow={eyebrow}
/>
{contact && (
<a className="cr-feature-link" href={contact.href}>
{contact.label}
<ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" />
</a>
)}
</div>
<div className="cr-faq-content">
<Accordion
items={items.map((item, index) => ({
value: String(index),
title: item.question,
content: item.answer,
}))}
/>
</div>
</div>
</section>
);
}
export function CtaBlock({
title,
description,
action,
eyebrow,
secondary,
}: {
title: string;
description: string;
action: { label: string; href: string };
eyebrow?: string;
secondary?: { label: string; href: string };
}) {
return (
<Card className="cr-cta-card">
<div className="cr-cta">
<div className="cr-cta-content">
{eyebrow && <p className="cr-marketing-eyebrow">{eyebrow}</p>}
<h2>{title}</h2>
<p className="cr-description">{description}</p>
<div className="cr-cta-actions">
<a className="cr-button" href={action.href}>
{action.label}
<ArrowRight size={17} strokeWidth={1.75} aria-hidden="true" />
</a>
{secondary && (
<a className="cr-feature-link" href={secondary.href}>
{secondary.label}
<ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" />
</a>
)}
</div>
</div>
<div className="cr-cta-emblem" aria-hidden="true">
<ArrowUpRight strokeWidth={1.75} />
</div>
</div>
</Card>
);
}
export function FooterBlock({
brand,
description,
groups,
copyright,
logo,
brandHref,
legalLinks,
}: {
brand: string;
description: string;
groups: Array<{
title: string;
links: Array<{ label: string; href: string }>;
}>;
copyright: string;
logo?: ReactNode;
brandHref?: string;
legalLinks?: Array<{ label: string; href: string }>;
}) {
const identity = (
<>
{logo && (
<span className="cr-footer-logo" aria-hidden="true">
{logo}
</span>
)}
<strong>{brand}</strong>
</>
);
return (
<footer className="cr-footer">
<div className="cr-footer-main">
<div className="cr-footer-brand">
{brandHref ? (
<a className="cr-footer-identity" href={brandHref}>
{identity}
</a>
) : (
<div className="cr-footer-identity">{identity}</div>
)}
<p className="cr-description">{description}</p>
</div>
<div className="cr-footer-navigation">
{groups.map((group) => (
<nav key={group.title} aria-label={group.title}>
<strong>{group.title}</strong>
{group.links.map((link) => (
<a key={link.href} href={link.href}>
{link.label}
</a>
))}
</nav>
))}
</div>
</div>
<div className="cr-footer-bottom">
<p className="cr-description">{copyright}</p>
{legalLinks?.length ? (
<nav aria-label="Legal">
{legalLinks.map((link) => (
<a href={link.href} key={link.href}>
{link.label}
</a>
))}
</nav>
) : null}
</div>
</footer>
);
}