Cards and grids
ListingCard shows one listing as a card: photo, price, address, specs and the listing brokerage. ListingGrid lays a list of them out in a responsive grid. Both render on the server with no client JavaScript, so they work in search results, building pages, home page sections and anywhere else you have listings.
import { ListingCard, ListingGrid } from '@sonordev/re-site-kit'ListingGrid
<ListingGrid
listings={listings}
hrefFor={(listing) => (listing.slug ? `/listings/${listing.slug}` : null)}
emptyState={<p>No homes match those filters.</p>}
/>| Prop | Type | What it does |
|---|---|---|
listings | RealEstateListing[] | The listings to show. Required. |
hrefFor | (listing) => string | null | Each card's link. Return null for no link. Leave the prop out and cards link to the listing's source_url. |
emptyState | ReactNode | Rendered instead of the grid when listings is empty. Without it, an empty list renders nothing. |
className | string | Added next to re-listing-grid. |
The grid auto-fills columns at least 320px wide with a 1.5rem gap. Every card in it credits the listing brokerage (see Brokerage credit).
Link to your own pages. Without hrefFor, cards link to source_url, the listing's page at its source, and open it in a new tab. On a site with a page per listing you'll almost always want hrefFor. Guard against listings without a slug so no card links to /listings/null:
hrefFor={(listing) => (listing.slug ? `/listings/${listing.slug}` : null)}A different layout. The grid's column rule is an inline style. For a carousel, a two-column layout or anything else, map ListingCard inside your own container:
<ul className="featured-homes">
{listings.map((listing) => (
<li key={listing.id}>
<ListingCard listing={listing} href={listing.slug ? `/listings/${listing.slug}` : null} />
</li>
))}
</ul>ListingCard
<ListingCard listing={listing} href="/listings/12-oak-st-springfield-123456" />| Prop | Type | Default | What it does |
|---|---|---|---|
listing | RealEstateListing | The listing. Required. | |
href | string | null | the listing's source_url | Where the card links. Pass null to render the card without a link. |
showAttribution | boolean | true | Show "Courtesy of" the listing brokerage. |
className | string | Added next to re-listing-card. |
What's on the card:
- The first photo, cropped to 16:10, lazy-loaded, with the street address as its alt text. With no photo, the frame keeps its shape on a gray background.
- The price, from
formatPrice(the feed's own display string when it has one). - A status badge, only when the listing isn't
active(for example "pending"). - The street address, the location and the specs ("3 bd · 2 ba · 1,960 sqft").
- "Courtesy of Example Realty", the listing brokerage, when the listing has a
listing_office_nameandshowAttributionis on.
Links. An href that starts with http:// or https:// opens in a new tab with rel="noopener noreferrer". A path like /listings/12-oak-st opens in the same tab. So pass a path, not a full URL, for pages on your own site.
Brokerage credit
MLS IDX rules generally require the listing office to be named wherever another broker's listing is shown, and that includes search results, not just the detail page. It's also the common case: a page of search results usually shows listings from many brokerages, and only a few are your own.
So ListingCard credits the listing brokerage by default, and ListingGrid always shows it. Style it with .re-listing-card__courtesy:
.re-listing-card__courtesy {
color: #4b5563;
text-transform: uppercase;
letter-spacing: 0.04em;
}Its size, opacity and margin are inline styles, so changing those takes !important (see Styling and performance).
Pass showAttribution={false} only where the surrounding UI already credits the office, for example in your own card layout that prints the brokerage elsewhere. More on what IDX asks for is in IDX compliance.
Styling
Both components ship a few inline layout essentials and a class on every element. The full list of classes, and how to override the inline styles, is in Styling and performance.