Skip to content

Components / Sections

Testimonial Marquee

Shows two rows of testimonial cards drifting in opposite directions, pausing while hovered so any quote can be read. Pure CSS; under reduced motion the rows become scrollable strips.

Live demo

Zero islands, same motion.

A. DeveloperAstro

The byte counter is the pitch.

B. Builder

Survived our ClientRouter migration.

C. Lead

Copied the source, owe nothing.

D. Indie

Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.

Variants · each row writes the attribute the prop writes; the section's own CSS answers

tone
density

Measured

JavaScript of its own

0 B

CSS 8.5 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Baseline · widely available

Chrome 120 · Firefox 53 · Safari 15.4; needs mask-image.

Install Pro

The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:

terminal
npx shadcn@latest add @moonarc-pro/testimonial-marquee
src/pages/index.astro
---
import TestimonialMarquee from '@/components/moonarc/pro/TestimonialMarquee.astro';
---
<TestimonialMarquee duration={60} quotes={[
  { quote: 'Zero islands, same motion.', name: 'A. Developer', role: 'Astro' },
  { quote: 'I can see what every component costs.', name: 'B. Builder' },
]} />

Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: marquee.

Props

PropTypeDefaultDescription
quotes{ quote, name, role? }[]noneSplit evenly across the rows.
durationnumber60Seconds per loop; the second row runs 8 s slower.
rows1 | 22One row, or two moving against each other.
tone'surface' | 'muted' | 'ink' | 'brand'unset (paints nothing)The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. Each paints --background, --ma-tone-muted (over --muted, with a --border hairline), --ma-tone-ink or --ma-tone-brand (over --primary) with the matching text. With theme.css, a painted tone also re-declares --card, --border, --input and --ring inside the section, so cards and fields follow the ground. Decoration is strongest on brand.
density'compact' | 'cozy' | 'roomy''cozy'Card padding and the row gap ×0.8 / ×1 / ×1.25 through --ma-density.

Theming and overrides

The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.

Classes

.ma-testimonial-marquee.ma-testimonial-marquee__card.ma-testimonial-marquee__copy.ma-testimonial-marquee__eyebrow.ma-testimonial-marquee__head.ma-testimonial-marquee__headline

Slots

slot="eyebrow"slot="headline"slot="copy"

Reduced motion

Rows stop and become scrollable strips.

With ClientRouter

CSS-only; nothing to rebind.

Why it is built this way

Replaces: Marquee testimonials (Magic UI) · InfiniteMovingCards (Aceternity). See the migration table.

Marquee

Ticker

Infinite scrolling strip for logos, quotes or tags in any direction.

every browseralwayshover

A "trusted by" section: a heading, a strip of greyscale logos drifting past that colour up on hover, and optionally the same logos as a hairline grid revealed with a stagger.

every browseralwaysscroll

Shows testimonial quotes in a staggered grid.

every browserscrollpointer

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown