Testimonial Wall
loomui/testimonial-wallFreeComponent
Columns of quotes drifting past each other at different speeds, faded at both ends.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/testimonial-wall.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"6import { useInViewport } from "@/registry/lib/use-in-viewport"78export interface TestimonialWallProps extends React.ComponentProps<"div"> {9 /** Columns at the widest breakpoint. Narrow screens drop the extras. */10 columns?: number11 /** Seconds for one pass of the first column. */12 duration?: number13 /** Per-column multipliers on `duration`. Falls back to a fixed drift. */14 speeds?: number[]15 /** Gap between cards and between columns, as a CSS length. */16 gap?: string17 /** Stop every column while the pointer is over the wall. */18 pauseOnHover?: boolean19 /** Render the wall static. */20 paused?: boolean21 /** Fade the top and bottom out. Pass a CSS length to size the fade. */22 fade?: boolean | string23}2425/**26 * Alpha sampled off a smoothstep curve. A two-stop gradient ramps linearly, and27 * the eye reads where that ramp meets full opacity as a hard line. Sampling the28 * curve rounds both ends off, so the fade runs out instead of stopping.29 */30const FADE_STOPS = [0, 0.0608, 0.216, 0.5, 0.784, 0.939, 1]3132function fadeMask(size: string) {33 const last = FADE_STOPS.length - 134 const ratio = (index: number) => (index / last).toFixed(4)3536 const leading = FADE_STOPS.map(37 (alpha, index) => `rgb(0 0 0 / ${alpha}) calc(${size} * ${ratio(index)})`38 )39 const trailing = FADE_STOPS.map(40 (_, index) =>41 `rgb(0 0 0 / ${FADE_STOPS[last - index]}) calc(100% - ${size} * ${ratio(42 last - index43 )})`44 )4546 return `linear-gradient(to bottom, ${[...leading, ...trailing].join(", ")})`47}4849/** Columns past the first are dropped rather than squeezed on small screens. */50const VISIBILITY = ["flex", "hidden sm:flex", "hidden lg:flex"]5152export function TestimonialWall({53 children,54 className,55 columns = 3,56 duration = 44,57 speeds,58 gap = "1rem",59 pauseOnHover = true,60 paused = false,61 fade = true,62 style,63 ...props64}: TestimonialWallProps) {65 const items = React.Children.toArray(children)66 // Dealt round robin, so a wall of uneven cards still balances out.67 const lanes = Array.from({ length: Math.max(columns, 1) }, (_, lane) =>68 items.filter((_, index) => index % Math.max(columns, 1) === lane)69 )7071 const mask = fade ? fadeMask(fade === true ? "14%" : fade) : undefined7273 const root = React.useRef<HTMLDivElement>(null)74 const onScreen = useInViewport(root)7576 return (77 <div78 ref={root}79 data-slot="testimonial-wall"80// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
