Property Listing Cards
Real estate listing cards with hover-zoom photos, price pills, beds, baths, area and location. React and Tailwind CSS, no dependencies.
PropertyListingCards.tsx
// Property listing cards: photo with hover zoom, title and price pill,
// then beds, baths, area and location with inline icons.
// Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.
type Listing = {
title: string;
price: string;
image: string;
beds: number;
baths: number;
area: string;
location: string;
href?: string;
};
type Props = {
heading?: string;
listings?: Listing[];
};
const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=900&q=80&auto=format&fit=crop`;
const DEFAULT_LISTINGS: Listing[] = [
{ title: "Modern Apartment", price: "$1,000/mo", image: img("1560184897-ae75f418493e"), beds: 1, baths: 1, area: "1,000", location: "Downtown" },
{ title: "Luxury Penthouse", price: "$15,000/mo", image: img("1545324418-cc1a3fa10c00"), beds: 3, baths: 3, area: "2,500", location: "Upper East Side" },
{ title: "Cozy Studio", price: "$500/mo", image: img("1522708323590-d24dbb6b0267"), beds: 1, baths: 1, area: "600", location: "Greenwich Village" },
];
const Icon = ({ d }: { d: string }) => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75} strokeLinecap="round" strokeLinejoin="round" aria-hidden className="h-4 w-4 shrink-0 md:h-5 md:w-5">
<path d={d} />
</svg>
);
const ICONS = {
bed: "M2 20v-8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v8M2 16h20M6 10V6a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v4",
bath: "M4 12h16a1 1 0 0 1 1 1v2a5 5 0 0 1-5 5H8a5 5 0 0 1-5-5v-2a1 1 0 0 1 1-1zM6 12V5a2 2 0 0 1 4 0",
area: "M3 3h18v18H3zM3 9h18M9 21V9",
pin: "M12 21s-7-6.2-7-11.5A7 7 0 0 1 19 9.5C19 14.8 12 21 12 21zM12 12a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z",
};
export default function ListingCards({ heading = "Featured properties", listings = DEFAULT_LISTINGS }: Props) {
return (
<section className="bg-[#f5f5f5] px-5 py-20 font-['Manrope',ui-sans-serif,system-ui,sans-serif] antialiased md:px-10">
<div className="mx-auto max-w-7xl">
<h2 className="mb-10 text-center font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-[clamp(32px,4vw,50px)] font-semibold leading-[1.2] text-black">{heading}</h2>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{listings.map((p) => (
<a key={p.title} href={p.href ?? "#"} className="group block h-full overflow-hidden bg-white">
<div className="overflow-hidden">
<img src={p.image} alt={p.title} className="h-[250px] w-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105" />
</div>
<div className="p-4">
<div className="flex items-center justify-between gap-4">
<h3 className="font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-xl font-semibold text-black">{p.title}</h3>
<span className="shrink-0 rounded-full bg-[#f5f5f5] px-4 py-2 text-sm font-semibold text-black">{p.price}</span>
</div>
<div className="mt-4 flex flex-col gap-3 text-sm text-neutral-500">
<div className="flex justify-between gap-2">
<span className="flex items-center gap-2">
<Icon d={ICONS.bed} /> {p.beds} {p.beds === 1 ? "Bed" : "Beds"}
</span>
<span className="flex items-center gap-2">
<Icon d={ICONS.bath} /> {p.baths} {p.baths === 1 ? "Bath" : "Baths"}
</span>
<span className="flex items-center gap-2">
<Icon d={ICONS.area} /> {p.area} sqft
</span>
</div>
<span className="flex items-center gap-2">
<Icon d={ICONS.pin} /> {p.location}
</span>
</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/PropertyListingCards.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


