PropertySearchHero
v1.0.4Updated 19 Oct 2026The front door of a property portal (fb_97d176de): a headline over a search FORM whose first control is the kind of home as a keyboard-operable tab row, then a buy-or-rent switch, a text box, and a row of shortcut chips each carrying a LIVE count the host supplies. It holds no results — it builds a query and hands it over, so a home page renders it before a single listing has been fetched (PropertyMapSearch and the PropertySearch page are the results surface). It is a real <form> with named fields, so the search works with JavaScript off; onSearch upgrades that to client navigation. The counts are never invented: a chip with no figure prints none, a figure still in flight shows a placeholder, and a failed count fetch drops every number and offers a retry.
Preview
Live · 14 stories▸Why this is recommendedheropropertyreal-estatesearch+11
The default, out-of-the-box PropertySearchHero.
- ✓A property portal home page with a search box for condo / house / land and buy or rent
- ✓A hero with a search form instead of a headline and a CTA
- ✓Shortcut chips that each show how many results they currently find
- ✓A rental or classifieds front page where the visitor picks a category then types a place
- ✓A search band that has to work from a server-rendered page with no JavaScript
- Kind of home as a real tablist over ONE shared panel: arrows, Home and End move between condo / house / land, moving also chooses, and the panel is never remounted so the caret in the search box survives every switch (which is why it is not built on Tabs — three panels around one text box would remount the input)
- Buy-or-rent as a radiogroup with the same arrow contract and one tab stop
- Shortcut chips with LIVE counts from the host: a count of 0 is printed because it is a true answer, a missing count prints nothing, countsState loading shows a placeholder per chip, and countsState error drops every number, says so once and offers a retry — there is no state where a stale figure sits beside a broken refresh
- The count is spelled into the chip aria-label (the visible number is aria-hidden), so every browser announces the same sentence instead of stitching two inline spans
- Works with JavaScript off: the tabs and the switch are buttons that never submit, their values ride hidden fields, and action / method / fieldNames point the GET at the portal own route
- Per-kind and per-deal counts on the tabs and the switch, under the same honesty rules as the chips
- A disabled option (a market not open yet) is visible, skipped by the arrow keys, and can never end up selected — and a stale kind in a bookmarked URL is corrected to a real one instead of leaving a tablist with nothing chosen
- Consumer safety: a chip href passes an allow-list before it becomes an anchor (relative or http(s) only by default, so a javascript: or data: address a seller typed is refused and the chip stays a working button), and every label is rendered as text with invisible and direction-reordering characters stripped
- Slots for the rest of the band: aside for the strip of live figures (StatCard), background for an image or gradient in an aria-hidden layer, footer for extra links, eyebrow as a node or false
- Controlled or uncontrolled query ({ kind, deal, query }), searching state on the button, disabled for the whole section, size sm / md / lg, heading level 1-3
- i18n labels prop (PropertySearchHeroLabels) with English defaults, covering the three built-in kinds and the two deals so a portal that passes no option data still translates; counts format through the locale provider
- heroValueToSearchParams turns the query into the parts of a URL that are actually set, and the same normalisation is importable from lib/property-search-hero so a server route can CALL it
- ✕Showing results: this section has no list and no map (use PropertyMapSearch or the PropertySearch page)
- ✕Filtering rows already on screen (use FilterBar or PropertyMapSearch filters)
- ✕A SaaS marketing hero with a headline and a CTA (use Hero)
- ✕Inventing the counts: the figures come from the host, and there is no demo data behind them
- ✕A full filter panel with price ranges and bedrooms (that belongs on the results screen)
Install
Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.
Usage
Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.
Props
kindsKinds of home in tab order: { value, label?, count?, disabled? }. Default condo / house / land, worded by the labels bag so they translate without passing data. A value the catalogue does not know is labelled by its own value, never by another kind word
—dealsBuy / rent (or lease, or auction), same shape. Default buy / rent
—valueControlled query { kind, deal, query }. A kind the portal no longer lists is corrected on the way out, so the tab row stays valid
—defaultValueUncontrolled initial query. Anything missing falls to the first choosable option
—onValueChangeFires on every change: a tab, the switch, a keystroke, a chip — with the whole query, ready for the URL
—onSearchThe visitor pressed Search or Enter. The native submit is prevented, so this is client navigation. Without it the form submits for real
—chipsShortcut chips: { id, label, count?, href?, value?, description?, disabled? }. count is a LIVE figure from the host; value is the part of the query the chip stands for, merged over the current one
—onChipSelectA chip was used. Fires alongside the chip own href, with the resulting query
—countsStateHow the live counts are doing. Default ready. loading shows a placeholder per chip; error prints no number at all and explains itself
—onCountsRetryRetry the counts. With none, a failed count fetch is still explained, without a dead button
—actionWhere a no-JavaScript submit goes. With none, the current URL — which is what a home page that reads its own query string wants
—methodSubmit method for that path. Default get
—fieldNamesRename the submitted fields. Defaults kind, deal and q
—searchingThe search is running — the button shows its busy state instead of looking like a dead press
—disabledEvery control inert, chips included
—titleReplace the headline. Defaults to the title label
—subtitleReplace the supporting line
—eyebrowSmall line above the headline (a Badge works well). false hides it
—headingLevelHeading level the title renders as. Default 1 — a hero usually owns the page, but not always
—backgroundA real element behind the content (image, gradient, video), rendered in an aria-hidden layer. A node, not a URL, so the host keeps control of loading and its own content policy
—asideA panel beside the form on wide screens — the strip of live figures a portal puts here, built from StatCard
—footerExtra nodes under the chips (a link to the map search, a phone number)
—sizeVertical density and headline scale. Default md
—hrefAllowedMay a chip link there? Default isSafePropertyHref: relative or http(s) only. Widen it deliberately for an app scheme
—formatCountFormat a live count. Default: through the locale provider
—labelsOverride every built-in string and aria template (i18n). Merged over DEFAULT_PROPERTY_SEARCH_HERO_LABELS; the defaults are English
—