The vacation rental card
View .mdEvery Kismet site shows homes as cards, and every site used to hand-roll the
same card: its own summary adapter, its own capacity string, its own dodge
for badge photos, its own way of carrying the canonical link. The SDK owns
the data side of all of it now. The card implements the property-card
contract — kismet check grades your card the same way it
grades ours.
The division of labor: the SDK reads data, the site speaks. The SDK decides which price applies, which photo is the cover, and which link a card opens — returned as structured values, never display strings. The site decides every word (“sleeps 4”, “4 beds”, “4 camas”) and every pixel.
The structured view
Section titled “The structured view”import { toVacationRentalCardView } from '@kismet-tech/sdk';
const cardView = toVacationRentalCardView(summary, datedPrice?);summary is structural: the mapping reads only slug, name, capacity,
location, media, and pricing, so every SDK summary shape
(VacationRentalSummary, VacationRentalDetail) satisfies it as-is — one
adapter, not two.
datedPrice carries the guest’s selected stay:
| Shape | Meaning |
|---|---|
{ status: 'priced', totalMinor, currency, nights } |
The dated stay total, before taxes and fees |
{ status: 'unavailable' } |
The home cannot serve the selected dates |
| omitted | No dates are set |
The view has no labels — only structured values:
price— which price applies and its numbers:{ kind: 'dated-total', amountMinor, currency, nights, beforeTax: true }(dates set),{ kind: 'from-nightly', amountMinor, currency, beforeTax: true }(no dates),{ kind: 'unavailable' }(dates set, home unavailable),{ kind: 'none' }(nothing known).beforeTax: truemarks the amount as excluding taxes and fees; saying so in words is the site’s job.capacity/location— the numbers and location parts with unknown fields dropped (nullwhen nothing is known).availability—'available' | 'unavailable'.coverPhoto,canonicalUrl,slug,name.
The optional English formatters
Section titled “The optional English formatters”import { formatCapacity, formatVacationRentalCardPrice, formatLocation, formatNightly, formatStayTotal,} from '@kismet-tech/sdk';The English wordings are OPTIONAL defaults — what the prebuilt card shows
when the site passes no wording — not THE formats. formatCapacity →
"2 bd · 2 ba · sleeps 6" (unknown fields drop; null when nothing is
known), formatLocation → "Government Camp, Oregon", formatNightly →
"From $240 a night", formatStayTotal → "$840 total · 3 nights",
formatVacationRentalCardPrice(price) → the default wording per structured kind
('unavailable' → "Not available for your dates", 'none' → null).
Each accepts an optional { locale } (default en-US). A site in another
language skips them entirely and formats the structured view itself — same
data, different words.
Cover photo
Section titled “Cover photo”import { chooseVacationRentalCoverPhoto } from '@kismet-tech/sdk';
const coverPhoto = chooseVacationRentalCoverPhoto(summary.media);The hero wins; otherwise the first gallery photo whose isBadge is not set.
Flag badge photos once at the data layer ({ url, alt, isBadge: true }) and
no card ever leads with an award sticker — the retired alternative was every
site hard-coding gallery[1] and hoping. No usable photo? The view’s
coverPhoto is undefined and the prebuilt card renders an
aria-hidden placeholder.
The React hook + prebuilt card
Section titled “The React hook + prebuilt card”import { Kismet } from '@kismet-tech/sdk';
<Kismet.VacationRentalCard summary={summary} datedPrice={datedPrice} />Kismet.VacationRentalCard renders semantic, style-free markup — an article marked
data-kismet-contract="property-card", the canonical link, the img, an
h3 name, the meta line, the price with its disclosure, and a badges
region. The wiring lives in the L2 useVacationRentalCard hook:
contractAttributes— the property-card contract marker + slug;canonicalLinkProps—href,data-kismet-slug, the slug field binding, and the home name as the accessible label (data, not wording — override it on your anchor);nameFieldAttributes/priceFieldAttributes— the auditable field markers (the price binds under thepricing.nightlyFromfield prefix, with the derivation named:.amountMinorfor the indicative nightly,.stayTotalfor the dated total).
The escalation levels, one contract constant:
- Take — render
<Kismet.VacationRentalCard>as-is;classNamecarries your look, and the default English wordings apply. - Reword —
format={{ capacity, location, price, locale, priceDisclosure }}: functions over the structured view in any language plus your locale and disclosure sentence. The structured view data is byte-identical to the English rendering’s — two sites, one SDK. - Restyle —
slots={{ media, title, meta, price, badges }}; every slot is a render prop over{ card }(theuseVacationRentalCardresult), so a replaced region keeps the wiring. - Extend —
badges={…}adds content inside the badges region.
CanonicalLink
Section titled “CanonicalLink”<Kismet.CanonicalLink slug={summary.slug}>{children}</Kismet.CanonicalLink>The markup-free seam behind the card’s links: one anchor to
/properties/{slug} carrying data-kismet-slug and
data-kismet-field="slug", no wrapper markup and no styles. With no
children the slug itself is the visible text. Build a fully custom card on
useVacationRentalCard + CanonicalLink and the property-card MUSTs stay satisfied
by construction.