Guest reviews in a three-column grid where every fourth card spans two columns, with source, stars, quote and avatar. React and Tailwind CSS.
ReviewsMasonryGrid.tsx
// Guest reviews on a sand background: a three-column grid where every fourth
// card spans two columns, each with source, star rating, quote and guest.
// Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.
type Review = { source: string; rating: number; quote: string; name: string; detail: string; avatar: string };
type Props = {
heading?: string;
reviews?: Review[];
};
const face = (id: string) => `https://images.unsplash.com/photo-${id}?w=120&h=120&q=80&auto=format&fit=crop&crop=faces`;
const DEFAULT_REVIEWS: Review[] = [
{ source: "Tripadvisor", rating: 5, quote: "The calmest four days we've had in years. Breakfast on the terrace alone was worth the trip.", name: "Hannah L.", detail: "Stayed in the Garden Suite", avatar: face("1499952127939-9bbf5af6c51c") },
{ source: "Google", rating: 5, quote: "Staff remembered our names from the first evening. Every detail felt considered.", name: "Marco B.", detail: "Anniversary weekend", avatar: face("1500648767791-00dcc994a43e") },
{ source: "Booking.com", rating: 5, quote: "Spotless rooms, a beautiful pool and the best night's sleep I've had in a hotel.", name: "Priya S.", detail: "Business trip", avatar: face("1508214751196-bcfd4ca60f91") },
{ source: "Tripadvisor", rating: 5, quote: "We booked two nights and stayed five. The spa, the food and the quiet: we did not want to leave, and we're already planning next summer.", name: "James & Ola", detail: "Family stay", avatar: face("1507003211169-0a1dd7228f2d") },
{ source: "Google", rating: 4, quote: "Lovely views and a very kind team. The chef's table is a must.", name: "Sofia R.", detail: "Weekend away", avatar: face("1546961329-78bef0414d7c") },
];
const Stars = ({ rating }: { rating: number }) => (
<div className="flex gap-0.5" aria-label={`${rating} out of 5 stars`}>
{Array.from({ length: 5 }).map((_, i) => (
<svg key={i} viewBox="0 0 24 24" aria-hidden className={`h-4 w-4 ${i < rating ? "fill-[#f59e2b]" : "fill-black/15"}`}>
<path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01z" />
</svg>
))}
</div>
);
export default function ReviewsMasonry({ heading = "What our guests say", reviews = DEFAULT_REVIEWS }: Props) {
return (
<section className="bg-[#f4e9e2] px-5 py-20 font-sans antialiased md:px-10 md:py-28">
<div className="mx-auto max-w-7xl">
<h2 className="mb-12 text-center font-['Cardo',Georgia,'Times_New_Roman',serif] text-4xl text-black md:mb-16 md:text-6xl">{heading}</h2>
<ul className="grid gap-6 md:grid-cols-3">
{reviews.map((r, i) => (
<li key={r.name} className={`flex flex-col bg-white p-6 ${i % 5 === 3 && i !== reviews.length - 1 ? "md:col-span-2" : ""}`}>
<p className="text-xl font-bold text-black">{r.source}</p>
<div className="mt-2">
<Stars rating={r.rating} />
</div>
<blockquote className="mt-4 grow text-sm leading-relaxed text-[#3b3b3b]">“{r.quote}”</blockquote>
<div className="mt-6 flex items-center gap-3">
<img src={r.avatar} alt={r.name} className="h-12 w-12 shrink-0 rounded-full object-cover" />
<div>
<p className="font-['Cardo',Georgia,'Times_New_Roman',serif] text-base font-bold text-black">{r.name}</p>
<p className="text-sm text-[#3b3b3b]">{r.detail}</p>
</div>
</div>
</li>
))}
</ul>
</div>
</section>
);
}
How to use it
- Make sure your Next.js or React project has Tailwind CSS set up (v3 or v4 both work).
- Create
components/ReviewsMasonryGrid.tsxand paste the code. - Import it into a page and replace the sample text and images with your own.
