Skip to content
KismetKismetDevelopers
llms.txt

The vacation rental card

View .md

Every 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.

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: true marks 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 (null when nothing is known).
  • availability — 'available' | 'unavailable'.
  • coverPhoto, canonicalUrl, slug, name.
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.

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.

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 the pricing.nightlyFrom field prefix, with the derivation named: .amountMinor for the indicative nightly, .stayTotal for the dated total).

The escalation levels, one contract constant:

  • Take — render <Kismet.VacationRentalCard> as-is; className carries 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 } (the useVacationRentalCard result), so a replaced region keeps the wiring.
  • Extend — badges={…} adds content inside the badges region.
<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.