Blocks

Grid Cascade

A three-up testimonial grid that pages a whole set at a time, cascading the next in from above.

A testimonial section that moves in sets, not slides. Paging drops the current three away on a stagger, and only once they are gone does the next set cascade in from above — so the two groups never overlap mid-flight. Wraps around at either end. GSAP-driven.

New tab

What our users say

Maya Collins

Maya Collins

Head of Operations

We replaced four tools with one workflow, and the team shipped its first release two weeks early. Nobody has asked to go back.
Ethan Brooks

Ethan Brooks

President of Sales

Reporting used to take a full day each month. Now it runs itself and lands in our inbox before standup.
Liam Harper

Liam Harper

Product Lead

Onboarding took an afternoon, not a quarter. Every handoff is visible, so nothing slips between design and build.

Installation

pnpm dlx shadcn@latest add @tween-ui/grid-cascade

Usage

app/page.tsx
app/page.tsx
import GridCascade from '@/components/tweenui/grid-cascade';
 
export default function Page() {
  return <GridCascade />;
}

Pass your own quotes:

app/page.tsx
app/page.tsx
import GridCascade from '@/components/tweenui/grid-cascade';
 
export default function Page() {
  return (
    <GridCascade
      title="What our users say"
      items={[
        {
          name: 'Maya Collins',
          position: 'Head of Operations',
          review: 'We replaced four tools with one workflow and shipped two weeks early.',
          image: '/people/maya.jpg',
          link: 'https://x.com/maya',
        },
        {
          name: 'Ethan Brooks',
          position: 'President of Sales',
          review: 'Reporting used to take a day a month. Now it lands in our inbox.',
          image: '/people/ethan.jpg',
        },
      ]}
    />
  );
}

Show more per page, or stop it wrapping:

<GridCascade perPage={2} loop={false} />

Props

PropTypeDefaultDescription
titleReactNodesample headingSection heading.
descriptionstring—Optional line under the heading.
itemsGridCascadeItem[]six quotesQuotes to page through.
perPagenumber3Cards shown at once.
loopbooleantrueWrap from the last page to the first.

Extends native <section> attributes (className, style, …).

GridCascadeItem

FieldTypeDescription
name · positionstringReviewer identity.
reviewstringThe quote. Clamped to three lines so a page stays level.
image · imageAltstringAvatar; imageAlt falls back to name.
linkstringAdds the corner button. Omit it and none renders.

The controls hide themselves when everything fits on one page, so passing three items with the default perPage gives a plain grid and no dead buttons.

Accessibility

Only the visible page is in the DOM, so a screen reader is never offered cards it cannot see. Both paging buttons are labelled, each star row carries its count, and profile buttons name the person they belong to. Under prefers-reduced-motion: reduce pages swap instantly with no drop or cascade.