Blog Cards
A blog preview with three post cards showing category, author, date, reading time and excerpt. React and Tailwind CSS, no dependencies.
BlogCards.tsx
// Blog preview: heading with an "All articles" link, then three post cards
// with category tag, author, date, reading time and excerpt.
// Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.
type Post = {
title: string;
excerpt: string;
category: string;
image: string;
author: { name: string; photo: string };
date: string;
minutes: number;
href?: string;
};
type Props = {
eyebrow?: string;
heading?: string;
allLink?: { label: string; href: string };
posts?: Post[];
};
const img = (id: string, w = 900) => `https://images.unsplash.com/photo-${id}?w=${w}&q=80&auto=format&fit=crop`;
const face = (id: string) => `https://images.unsplash.com/photo-${id}?w=80&h=80&q=80&auto=format&fit=crop&crop=faces`;
const DEFAULT_POSTS: Post[] = [
{ title: "Ten questions to ask before you make an offer", excerpt: "From survey results to service charges, the details that separate a good buy from an expensive lesson.", category: "Buying", image: img("1512917774080-9991f1c4c750"), author: { name: "Alexandra Morrison", photo: face("1573496359142-b8d87734a5a2") }, date: "Mar 12, 2026", minutes: 6 },
{ title: "Staging a home that sells in the first week", excerpt: "Light, space and a little restraint: what buyers notice in the first thirty seconds of a viewing.", category: "Selling", image: img("1560184897-cca79b749615"), author: { name: "Daniel Mercer", photo: face("1507003211169-0a1dd7228f2d") }, date: "Feb 28, 2026", minutes: 4 },
{ title: "Is now a good time to buy a holiday home?", excerpt: "Rates, rental yields and the coastal towns where prices are still sensible this year.", category: "Market", image: img("1564013799919-ab600027ffc6"), author: { name: "Isabelle Torres", photo: face("1534528741775-53994a69daeb") }, date: "Feb 9, 2026", minutes: 8 },
];
const Arrow = ({ className = "" }: { className?: string }) => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" aria-hidden className={className}>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
);
export default function BlogCards({
eyebrow = "Insights & advice",
heading = "Latest from our blog",
allLink = { label: "All articles", href: "#" },
posts = DEFAULT_POSTS,
}: Props) {
return (
<section className="bg-[#faf7f2] px-5 py-20 font-['Manrope',ui-sans-serif,system-ui,sans-serif] antialiased md:px-10 md:py-28">
<div className="mx-auto max-w-7xl">
<div className="mb-14 flex flex-wrap items-end justify-between gap-6">
<div>
<span className="mb-3 block text-xs font-bold uppercase tracking-[0.2em] text-[#F66F00]">{eyebrow}</span>
<h2 className="font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-[clamp(36px,5vw,64px)] font-semibold leading-[1.1] text-neutral-900">{heading}</h2>
</div>
<a href={allLink.href} className="inline-flex items-center gap-2 rounded-full border border-neutral-900 px-6 py-3 text-sm font-semibold text-neutral-900 transition-colors hover:bg-neutral-900 hover:text-white">
{allLink.label} <Arrow className="h-4 w-4" />
</a>
</div>
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<a key={post.title} href={post.href ?? "#"} className="group block overflow-hidden bg-white transition-shadow duration-300 hover:shadow-xl">
<div className="relative aspect-[16/9] overflow-hidden">
<img src={post.image} alt={post.title} className="absolute inset-0 h-full w-full object-cover transition-transform duration-700 group-hover:scale-105" />
<span className="absolute left-3 top-3 rounded-full bg-white/95 px-3 py-1 text-[0.65rem] font-semibold uppercase tracking-widest text-black">{post.category}</span>
</div>
<div className="p-6">
<div className="mb-4 flex items-center gap-3">
<img src={post.author.photo} alt={post.author.name} className="h-8 w-8 rounded-full object-cover" />
<div>
<div className="text-xs font-semibold text-neutral-900">{post.author.name}</div>
<div className="text-xs text-neutral-400">{post.date}</div>
</div>
<div className="ml-auto text-xs text-neutral-400">{post.minutes} min read</div>
</div>
<h3 className="mb-3 line-clamp-2 font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-xl font-semibold leading-snug text-neutral-900">{post.title}</h3>
<p className="line-clamp-3 text-sm leading-relaxed text-neutral-500">{post.excerpt}</p>
<div className="mt-5 flex items-center gap-1.5 text-sm font-semibold text-[#F66F00]">
Read more <Arrow className="h-3.5 w-3.5 transition-transform group-hover:translate-x-1" />
</div>
</div>
</a>
))}
</div>
</div>
</section>
);
}
How to use it
- Make sure your Next.js or React project has Tailwind CSS set up.
- Create
components/BlogCards.tsxand paste the code. - Import it into a page and replace the placeholder text and images with your own.
More free components
All features →
Ready for a faster site that's easier to edit?
Book a free 30-minute call. We'll review your current site and send a fixed-price plan. No pressure, no pitch.
Book Free Call No commitment required


