Retour au catalogue

About Quote Wall

Mur de citations et principes fondateurs en layout disperse.

aboutmedium Both Responsive a11y
editorialelegantagencyportfoliouniversalmasonry
Theme

How to build a quote wall grid in React with staggered animations

A React quote wall lays variable-size cards in a CSS grid and animates each card into view with Framer Motion's whileInView, staggering delays by index and adding a small alternating rotation that settles to zero, no JavaScript masonry library required.

  • Stack: React + Framer Motion + Tailwind v4 + lucide-react (Quote icon), ~67 lines total.
  • Layout engine: CSS grid with col-span classes (1, 2, or 3 columns), no JS masonry.
  • Animation: whileInView with once:true, 0.08s stagger per card, ±2° rotation on enter that settles to 0.
  • Fully theme-aware: every color is a CSS custom property (--color-background, --color-accent, --color-border).
  • Accessible: semantic <section> and <p> markup; Quote icon is decorative (aria-hidden implicit).

About Quote Wall is a React section that turns a set of founder quotes, company values, or testimonials into a living mosaic. Cards come in three sizes that span different column counts, creating an editorial-magazine feel without any masonry library. Each card tilts slightly on entry then straightens as it settles into place.

Anatomy

The component has two parts. At the top, a centered header with an optional badge (pill with border) and an h2 title, both wrapped in a single whileInView motion.div that fades up. Below, a CSS grid with `grid-cols-1 md:grid-cols-3 lg:grid-cols-6` lays out the quote cards. Each card is a motion.div with a rounded border, a semi-transparent Quote icon at top-left, serif italic body text, and a bottom attribution block (name + optional role) separated by a hairline border.

How it works

Each card initialises with `opacity: 0`, `y: 20`, and a rotation of `+2°` (even index) or `-2°` (odd index). As it enters the viewport, whileInView drives it to `opacity: 1`, `y: 0`, `rotate: 0` over 0.5 s. The stagger is manual: `delay: i * 0.08`, so the 8th card starts 640 ms after the first, reading left-to-right like a reveal. The `once: true` viewport option means the animation fires once per session, keeping scroll performance clean.

How to build it in React

  1. Define the data shape and size map

    Create a QuoteItem interface with id, text, author, optional role, and an optional size ('small' | 'medium' | 'large'). Map sizes to Tailwind col-span classes and text-size classes up front so the render loop stays simple.

    const sizeClasses = {
      small:  "col-span-1",
      medium: "col-span-1 md:col-span-2",
      large:  "col-span-1 md:col-span-2 lg:col-span-3",
    };
  2. Build the CSS grid container

    Use `grid-cols-1 md:grid-cols-3 lg:grid-cols-6` with `auto-rows-auto` so cards only take the height they need. Six columns at large breakpoint lets a 'large' card fill half the row, a 'medium' card a third, and a 'small' card a sixth.

    <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-4 auto-rows-auto">
  3. Animate each card with rotation stagger

    Wrap each card in a motion.div. Set the initial rotation to `(i % 2 === 0 ? 1 : -1) * 2` degrees so adjacent cards lean in opposite directions on entry. Drive everything to zero in whileInView with a per-card delay.

    <motion.div
      initial={{ opacity: 0, y: 20, rotate: (i % 2 === 0 ? 1 : -1) * 2 }}
      whileInView={{ opacity: 1, y: 0, rotate: 0 }}
      viewport={{ once: true }}
      transition={{ delay: i * 0.08, duration: 0.5 }}
    >
  4. Compose the card interior

    Place the Quote icon (lucide-react) at 50% opacity as a decorative marker, then the quote text in a serif italic paragraph, then an attribution block separated by a border-t. All colors reference CSS custom properties so the card inherits any active theme preset.

    <Quote className="h-5 w-5 mb-3" style={{ color: "var(--color-accent)", opacity: 0.5 }} />
    <p className="font-serif italic">&ldquo;{quote.text}&rdquo;</p>
    <div className="mt-4 pt-3 border-t" style={{ borderColor: "var(--color-border)" }}>
      <p className="text-xs font-semibold">{quote.author}</p>
    </div>

When to use it

This section works well as a culture or values block on an agency or portfolio about page, a startup's 'what we believe' section, or a product page where social proof comes from founders rather than users. Pair it after a narrative about paragraph. Skip it for high-volume testimonial pages (20+ reviews) where a carousel or paginated list fits better, and for conversion-critical pages where focused copy matters more than a mosaic.

Used by

  • Notion, Uses a wall of founder and team quotes on its about page to convey company philosophy.
  • Basecamp, Presents company values and principles as standalone quote-style blocks in a dispersed editorial layout.
  • Mailchimp, Mixes pull-quotes and founder statements in a mosaic grid on its culture and about pages.

FAQ

How do I control which cards are wide?

Set size: 'medium' or size: 'large' on individual QuoteItem objects in your data array. Medium cards span 2 of 3 columns at md breakpoint; large cards span 3 of 6 at lg. Small cards always take 1 column.

Can I use this for customer testimonials instead of internal quotes?

Yes, the data shape is identical, swap the author/role values for customer name and company title. For social proof at scale (50+ testimonials), filter down to 5-8 highlights before passing them in; the grid does not paginate.

Does the rotation stagger work on mobile?

The animation itself runs fine on mobile. The grid collapses to a single column, so all size variants stack full-width. The rotation and stagger are still visible as the user scrolls down.

How do I change the font to match my brand?

The quote text uses Tailwind's font-serif utility. Override the serif stack globally via your Tailwind config or the @font-face rule for your chosen typeface, no changes needed inside the component.

"use client";

import React from "react";
import { motion } from "framer-motion";
import { Quote } from "lucide-react";

interface QuoteItem {
  id: string;
  text: string;
  author: string;
  role?: string;
  size?: "small" | "medium" | "large";
}

interface AboutQuoteWallProps {
  badge?: string;
  title?: string;
  quotes?: QuoteItem[];
}

export default function AboutQuoteWall({ badge, title, quotes = [] }: AboutQuoteWallProps) {
  const sizeClasses: Record<string, string> = {

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React Quote Wall Grid, Masonry Layout + Framer Motion