Listings
Listings Featured
A split title above five listing cards, the first twice as wide; photos zoom on hover.
Layout and motion from the Findit template — see it in contextimport Link from "next/link";
import { ZoomCard } from "@/components/ui/zoom-card";
import { cn } from "@/lib/utils";
/*
* Listings Featured — a split title (heading left, lead right) above five listing cards, the first one twice as wide.
* Source: marcframe/findit → components/sections/FeaturedSection.tsx (+ design-system SectionTitle split,
* PropertyCard simple, Chip).
*
* Layout kept from Framer: the lead sits beside the heading from 768px, aligned to its bottom (up to 400px). Cards: one
* column of four below 768px; two columns from 768px with the first card across both; three columns from 1024px with
* the first card two columns wide. Each card: a 350px photo with the category (a link) and the type as chips in its
* top-left corner, then the title and a one-line address on the muted surface.
* Motion kept: hovering a card grows its photo to 105% (zoom-card, Framer's 400ms tween where Findit uses a no-bounce
* spring). The photo link duplicates the title link, so it is hidden from assistive tech and the keyboard.
* Dropped: Findit's dotted, uppercase label.
*/
export type FeaturedListing = {
title: string;
href: string;
address: string;
image: { src: string; alt: string };
category?: { label: string; href?: string };
/** "For sale", "For rent"… */
type?: string;
};
export type ListingsFeaturedProps = {
/** Small line above the heading. */
label?: string;
title?: string;
lead?: string;
listings?: FeaturedListing[];
className?: string;
};
const photo = (id: string, w = 1200) => `https://images.unsplash.com/${id}?auto=format&fit=crop&w=${w}&q=80`;
const defaults = {
label: "Featured properties",
title: "Explore our featured listings",
lead: "From modern city apartments to family homes with a garden — a few of the places we're proudest to show this month.",
listings: [
{
title: "Marble House",
href: "/properties/marble-house",
address: "161 Bayview Drive, Coral Hills",
image: { src: photo("photo-1600596542815-ffad4c1539a9", 1600), alt: "White modern villa with a swimming pool" },
category: { label: "Villas", href: "/properties?category=villas" },
type: "For sale",
},
{
title: "Family Mansion",
href: "/properties/family-mansion",
address: "24 Orchard Lane, Maple Grove",
image: { src: photo("photo-1570129477492-45c003edd2be"), alt: "White house with a wraparound porch and a green lawn" },
category: { label: "Houses", href: "/properties?category=houses" },
type: "For sale",
},
{
title: "South Side Garden",
href: "/properties/south-side-garden",
address: "8 Elm Court, Southbank",
image: { src: photo("photo-1600585154340-be6161a56a0c"), alt: "Modern house with glass walls and a lawn at dusk" },
category: { label: "Houses", href: "/properties?category=houses" },
type: "For rent",
},
{
title: "Modern Family Home",
href: "/properties/modern-family-home",
address: "92 Harbor Road, Westport",
image: { src: photo("photo-1580587771525-78b9dba3b914"), alt: "Modern house with terraces above a swimming pool" },
category: { label: "Villas", href: "/properties?category=villas" },
type: "For sale",
},
{
title: "Rustic Forest Cabin",
href: "/properties/rustic-forest-cabin",
address: "3 Pine Ridge Trail, Cedar Falls",
image: { src: photo("photo-1568605114967-8130f3a36994"), alt: "Wooden house with lit windows at dusk" },
category: { label: "Cabins", href: "/properties?category=cabins" },
type: "For rent",
},
] as FeaturedListing[],
};
export function ListingsFeatured({
label = defaults.label,
title = defaults.title,
lead = defaults.lead,
listings = defaults.listings,
className,
}: ListingsFeaturedProps) {
return (
<section className={cn("py-(--section-y)", className)}>
<div className="mx-auto flex max-w-(--container) flex-col gap-12 px-(--gutter)">
<div className="flex flex-col gap-5 md:flex-row md:items-end md:gap-12 lg:gap-24">
<div className="flex flex-col gap-2.5 md:flex-1">
{label && <p className="text-sm font-medium text-muted-foreground">{label}</p>}
<h2 className="font-heading text-h2 font-semibold tracking-tight text-balance">{title}</h2>
</div>
{lead && <p className="max-w-100 text-pretty text-muted-foreground md:flex-1">{lead}</p>}
</div>
<div className="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
{listings.slice(0, 5).map((listing, i) => (
<article
key={listing.href}
className={cn(
"group flex flex-col overflow-hidden rounded-xl bg-muted",
i === 0 && "md:col-span-2",
i === 4 && "max-md:hidden",
)}
>
<div className="relative h-87.5">
<Link href={listing.href} tabIndex={-1} aria-hidden className="absolute inset-0 block">
<ZoomCard className="size-full rounded-xl">
<img src={listing.image.src} alt={listing.image.alt} className="object-cover" />
</ZoomCard>
</Link>
{(listing.category || listing.type) && (
<div className="absolute top-5 left-5 flex items-center gap-1.5">
{listing.category &&
(listing.category.href ? (
<Link
href={listing.category.href}
className="rounded-full bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
>
{listing.category.label}
</Link>
) : (
<span className="rounded-full bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground">
{listing.category.label}
</span>
))}
{listing.type && (
<span className="rounded-full bg-background px-3 py-1.5 text-xs font-medium text-foreground">
{listing.type}
</span>
)}
</div>
)}
</div>
<Link href={listing.href} className="flex flex-col gap-2.5 p-5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50">
<h3 className="font-heading text-lg font-semibold tracking-tight">{listing.title}</h3>
<p className="truncate text-sm text-foreground/65">{listing.address}</p>
</Link>
</article>
))}
</div>
</div>
</section>
);
}Installation
$ npx shadcn@latest add https://framebrick.co/r/blocks/listings-featured.json1. Add the Framebrick UI components it uses (they bring Motion).
npx shadcn@latest add https://framebrick.co/r/zoom-card.json2. Add the block tokens to
app/globals.cssonce (theming).npx shadcn@latest add https://framebrick.co/r/blocks/tokens.json3. Copy the block into
components/listings-featured.tsx.import Link from "next/link"; import { ZoomCard } from "@/components/ui/zoom-card"; import { cn } from "@/lib/utils"; /* * Listings Featured — a split title (heading left, lead right) above five listing cards, the first one twice as wide. * Source: marcframe/findit → components/sections/FeaturedSection.tsx (+ design-system SectionTitle split, * PropertyCard simple, Chip). * * Layout kept from Framer: the lead sits beside the heading from 768px, aligned to its bottom (up to 400px). Cards: one * column of four below 768px; two columns from 768px with the first card across both; three columns from 1024px with * the first card two columns wide. Each card: a 350px photo with the category (a link) and the type as chips in its * top-left corner, then the title and a one-line address on the muted surface. * Motion kept: hovering a card grows its photo to 105% (zoom-card, Framer's 400ms tween where Findit uses a no-bounce * spring). The photo link duplicates the title link, so it is hidden from assistive tech and the keyboard. * Dropped: Findit's dotted, uppercase label. */ export type FeaturedListing = { title: string; href: string; address: string; image: { src: string; alt: string }; category?: { label: string; href?: string }; /** "For sale", "For rent"… */ type?: string; }; export type ListingsFeaturedProps = { /** Small line above the heading. */ label?: string; title?: string; lead?: string; listings?: FeaturedListing[]; className?: string; }; const photo = (id: string, w = 1200) => `https://images.unsplash.com/${id}?auto=format&fit=crop&w=${w}&q=80`; const defaults = { label: "Featured properties", title: "Explore our featured listings", lead: "From modern city apartments to family homes with a garden — a few of the places we're proudest to show this month.", listings: [ { title: "Marble House", href: "/properties/marble-house", address: "161 Bayview Drive, Coral Hills", image: { src: photo("photo-1600596542815-ffad4c1539a9", 1600), alt: "White modern villa with a swimming pool" }, category: { label: "Villas", href: "/properties?category=villas" }, type: "For sale", }, { title: "Family Mansion", href: "/properties/family-mansion", address: "24 Orchard Lane, Maple Grove", image: { src: photo("photo-1570129477492-45c003edd2be"), alt: "White house with a wraparound porch and a green lawn" }, category: { label: "Houses", href: "/properties?category=houses" }, type: "For sale", }, { title: "South Side Garden", href: "/properties/south-side-garden", address: "8 Elm Court, Southbank", image: { src: photo("photo-1600585154340-be6161a56a0c"), alt: "Modern house with glass walls and a lawn at dusk" }, category: { label: "Houses", href: "/properties?category=houses" }, type: "For rent", }, { title: "Modern Family Home", href: "/properties/modern-family-home", address: "92 Harbor Road, Westport", image: { src: photo("photo-1580587771525-78b9dba3b914"), alt: "Modern house with terraces above a swimming pool" }, category: { label: "Villas", href: "/properties?category=villas" }, type: "For sale", }, { title: "Rustic Forest Cabin", href: "/properties/rustic-forest-cabin", address: "3 Pine Ridge Trail, Cedar Falls", image: { src: photo("photo-1568605114967-8130f3a36994"), alt: "Wooden house with lit windows at dusk" }, category: { label: "Cabins", href: "/properties?category=cabins" }, type: "For rent", }, ] as FeaturedListing[], }; export function ListingsFeatured({ label = defaults.label, title = defaults.title, lead = defaults.lead, listings = defaults.listings, className, }: ListingsFeaturedProps) { return ( <section className={cn("py-(--section-y)", className)}> <div className="mx-auto flex max-w-(--container) flex-col gap-12 px-(--gutter)"> <div className="flex flex-col gap-5 md:flex-row md:items-end md:gap-12 lg:gap-24"> <div className="flex flex-col gap-2.5 md:flex-1"> {label && <p className="text-sm font-medium text-muted-foreground">{label}</p>} <h2 className="font-heading text-h2 font-semibold tracking-tight text-balance">{title}</h2> </div> {lead && <p className="max-w-100 text-pretty text-muted-foreground md:flex-1">{lead}</p>} </div> <div className="grid gap-5 md:grid-cols-2 lg:grid-cols-3"> {listings.slice(0, 5).map((listing, i) => ( <article key={listing.href} className={cn( "group flex flex-col overflow-hidden rounded-xl bg-muted", i === 0 && "md:col-span-2", i === 4 && "max-md:hidden", )} > <div className="relative h-87.5"> <Link href={listing.href} tabIndex={-1} aria-hidden className="absolute inset-0 block"> <ZoomCard className="size-full rounded-xl"> <img src={listing.image.src} alt={listing.image.alt} className="object-cover" /> </ZoomCard> </Link> {(listing.category || listing.type) && ( <div className="absolute top-5 left-5 flex items-center gap-1.5"> {listing.category && (listing.category.href ? ( <Link href={listing.category.href} className="rounded-full bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50" > {listing.category.label} </Link> ) : ( <span className="rounded-full bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground"> {listing.category.label} </span> ))} {listing.type && ( <span className="rounded-full bg-background px-3 py-1.5 text-xs font-medium text-foreground"> {listing.type} </span> )} </div> )} </div> <Link href={listing.href} className="flex flex-col gap-2.5 p-5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"> <h3 className="font-heading text-lg font-semibold tracking-tight">{listing.title}</h3> <p className="truncate text-sm text-foreground/65">{listing.address}</p> </Link> </article> ))} </div> </div> </section> ); }
Usage
import { ListingsFeatured } from "@/components/listings-featured";
<ListingsFeatured
title="Explore our featured listings"
listings={[
{
title: "Marble House",
href: "/properties/marble-house",
address: "161 Bayview Drive",
image: { src: "/listings/marble.jpg", alt: "…" },
category: { label: "Villas", href: "/properties?category=villas" },
type: "For sale",
},
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | Featured properties | Small muted line above the heading. |
| title / lead | string | — | Heading (h2) and the lead beside it from 768px (up to 400px). |
| listings | { title, href, address, image, category?, type? }[] | 5 listings | The first five show; the first is twice as wide. The category chip can link. |
| className | string | — | Classes for the <section>. |
- Below 768px one column of four; from 768px two columns with the first card across both; from 1024px three columns with the first card two wide.
- Hovering a card grows its photo to 105% (zoom-card). The photo link duplicates the title link, so it is hidden from assistive tech and the keyboard.
AI prompt
Paste into v0, Lovable, Bolt or Claude to rebuild this block in your own project.
Create a React + Tailwind CSS block called ListingsFeatured (Next.js). - <section className="py-(--section-y)">, container mx-auto max-w-(--container) px-(--gutter), column gap-12. - Split title: a small muted label and an h2 (font-heading text-h2 semibold); beside it from 768px (items-end, gap-12 / lg:gap-24) a muted lead up to 400px. - Grid (gap-5): 1 column (four cards) / 2 columns from 768px with the first card spanning both / 3 columns from 1024px with the first card spanning two. Card (group, rounded-xl bg-muted, overflow-hidden): a 350px photo (rounded-xl) that zooms to 1.05 while the card is hovered (400ms, cubic-bezier(0.12,0.23,0.5,1)), inside an aria-hidden link; chips at its top-left (the category rounded-full bg-primary text-primary-foreground text-xs, a link; the type bg-background); then p-5: an h3 title (font-heading text-lg semibold) and a one-line muted address, as one link. - Only shadcn variables and block tokens (secondary text on bg-muted is text-foreground/65: muted-foreground falls under 4.5:1 there); plain <img> with alt text.











