Every listings component renders a built-in layout by default. To use your own HTML
instead, put a <template> with {{ ... }} placeholders inside the component — the SDK
fills it with live listing data and keeps it in sync.
If you don't add a template, the built-in layout is used, so this is fully opt-in.
<rechat-map-listings-grid>
<template rechat-each="listings">
<a class="card" href="/listings/{{ id }}">
<img src="{{ cover_image_url }}" alt="">
<div class="price" data-format="currency">{{ price }}</div>
<div>{{ formatted.street_address.text }}</div>
<span data-format="pluralize:bed">{{ property.bedroom_count }}</span>
</a>
</template>
<template rechat-state="empty"><p>No listings match your search.</p></template>
</rechat-map-listings-grid>
rechat-each="listings" repeats its content once per listing. rechat-state="empty" (or
"loading") shows content only in that state.
Write {{ path }} in text or attributes. Paths are dotted property names only — no
logic, function calls, or expressions. A missing value renders as empty.
<h2>{{ formatted.price.text }}</h2>
<img src="{{ cover_image_url }}">
<a href="/p/{{ id }}">Details</a>
Commonly used fields on a listing:
| Path | Example |
|---|---|
id, status |
…, Active |
price |
1250000 |
cover_image_url |
image URL |
formatted.price.text |
$1,250,000 |
formatted.street_address.text |
123 Main St |
formatted.bedroom_count.value |
3 |
formatted.total_bathroom_count.value |
2.5 |
formatted.square_feet.text_no_label |
2,500 |
property.address.city |
Dallas |
formatted.* fields are ready-to-display strings; use them for prices, counts, and
addresses.
Add data-format="name" to an element that wraps a single {{ }} value:
| Formatter | Example |
|---|---|
currency |
1250000 → $1,250,000 |
number |
2500 → 2,500 |
date |
Mar 5, 2024 |
pluralize:bed / pluralize:bath |
3 beds / 1 bath |
uppercase / lowercase |
case change |
<span data-format="currency">{{ price }}</span>
data-if="path" removes an element when the value is empty/false:
<span class="badge" data-if="status">{{ status }}</span>
| Component | Directive | Renders |
|---|---|---|
<rechat-map-listings-grid> |
rechat-each="listings" |
your card, per listing |
<rechat-listing-details> |
rechat-details (single listing) |
your details-modal body |
<rechat-property-search-form> |
rechat-form |
your search form |
<rechat-filter-*> |
inner <template> |
your filter dropdown (see below) |
In <rechat-listing-details> the single listing is under listing, e.g.
{{ listing.formatted.price.text }}.
<rechat-filter-*> accept a <template> whose controls bind back to the search with
rechat-model:
<rechat-filter-price>
<template>
<button rechat-toggle>{{ label }}</button>
<div data-if="isOpen">
<select rechat-model="minimumPrice">…</select>
<select rechat-model="maximumPrice">…</select>
<button rechat-reset>Clear</button>
</div>
</template>
</rechat-filter-price>
rechat-model="key" two-way binds an <input>/<select> to a filter value.rechat-toggle / rechat-open / rechat-close control the dropdown; rechat-reset
clears the filter.isOpen, label, badge, hasValue (beds/baths also min).<script>, inline event handlers (onclick), and
style="{{…}}" are not allowed, and javascript:/data: URLs are blocked.id as results change, so the DOM isn't rebuilt on every
update.