Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
  1. Home
  2. Blog
  3. Build it: screens and apps
  4. Nextjs Real Estate Template: Search, Map and Mortgage
Auf Deutsch lesen
  1. Blog
  2. Build it: screens and apps
  3. Nextjs Real Estate Template: Search, Map and Mortgage
Build it: screens and apps
Build it: screens and apps

Nextjs Real Estate Template: Search, Map and Mortgage

SR
Serban Rusu · Founder of Wingo UI
Nov 6, 2026 · 10 min read

On this page

0%
  1. What does a real estate website template need?
  2. How do you add the template to a Next.js app?
  3. How should the buy or rent search work?
  4. What goes on a property listing card?
  5. How does the price pin map work, and when do you need a real map?
  6. What happens on a phone?
  7. How does the mortgage estimate calculate the payment?
  8. How do you wire the valuation form?
  9. Which parts are free and which are Pro?
  10. Try it before you install it
  11. FAQ
    1. Is there a free Next.js real estate template?
    2. What pages does a real estate website need?
    3. How should a property search map work on mobile?
    4. How is a monthly mortgage payment calculated?
    5. Does the template need a Google Maps or Mapbox key?

TL;DR

A nextjs real estate template needs more than a hero and a grid of houses: a buy or rent search that filters live, listing cards with price and facts, a map with price pins, neighborhood tiles, a mortgage estimate and a valuation form that captures sellers. The Wingo UI Real Estate Page (Pro) ships all of them in one client component, with callbacks for search, saved homes and valuation requests. For a results page with hundreds of homes, add the Listing Grid and a List / Map toggle on phones.

Published Nov 6, 2026

You are building a site for an agency or a listings startup, and the next js real estate template you find usually ships a hero, a grid of houses and a contact form. A nextjs real estate template that buyers use needs more than that. Buyers switch between buying and renting, narrow by price and bedrooms, look at where the homes are, and want to know what the monthly payment would be before they call anyone. Sellers want to know what their home is worth. This guide walks through each piece of a real estate site that buyers use, how the Wingo UI Real Estate Page handles it, and how the layout changes on a phone. We build Wingo UI, so we are not neutral; the patterns work with any components.

What does a real estate website template need?

Seven sections, in roughly this order, because that is the order of the questions a visitor has:

SectionThe visitor's questionWhat it needs
Search"Is there anything for me?"Buy or rent, location, price, beds, a live count
Featured homes"Show me"Cards with photo, price, beds, baths, area, a heart
Map"Where are they?"Price pins and a list that preview a home without leaving the page
Neighborhoods"Which area fits?"Median price or rent, trend, homes listed
Mortgage"Can I afford it?"Price, down payment, rate, term, the monthly total and its parts
Agents"Who do I talk to?"Name, areas, languages, Call and Email
Valuation"What is my home worth?"A short form that turns an owner into a lead

As of November 6, 2026, the Homely template on WrapPixel (opens in a new tab) costs $29 for a single use license or $59 for multiple use and lists a home page, a property listing, a property detail, a blog, a contact page and a 404 on Next.js 15, React 19, Tailwind v4 and shadcn. The Clay template on Sanity (opens in a new tab) is paid, runs on Next.js 14 with Sanity as the CMS and adds a blog and an agent contact form through Resend. Neither listing mentions a map, a mortgage estimate or a valuation form. For a CMS-backed site where you build search yourself, those are a fair start.

How do you add the template to a Next.js app?

Install it with the CLI. It copies the page, its sample data and every component it uses into your project as source:

bash
npx wingo-ui@latest add real-estate-page

The page is a client component. Load your listings on the server and hand them, with your handlers, to a small client view:

tsx
// app/page.tsx
import { getListings, getNeighborhoods } from "@/lib/listings"
import { HomesView } from "./homes-view"
export default async function Page() {
const [listings, neighborhoods] = await Promise.all([getListings(), getNeighborhoods()])
return <HomesView listings={listings} neighborhoods={neighborhoods} />
}
tsx
// app/homes-view.tsx
"use client"
import Link from "next/link"
import { RealEstatePage, type RealEstateSearch } from "@/components/templates/real-estate-page"
import type { RealEstateListing, RealEstateNeighborhood } from "@/components/templates/real-estate-page-data"
import { requestValuation, saveHome } from "./actions"
export function HomesView({
listings,
neighborhoods,
}: {
listings: RealEstateListing[]
neighborhoods: RealEstateNeighborhood[]
}) {
const syncUrl = ({ mode, location, price, minBeds }: RealEstateSearch) => {
const params = new URLSearchParams({ mode })
if (location) params.set("loc", location)
if (price[0] != null) params.set("min", String(price[0]))
if (price[1] != null) params.set("max", String(price[1]))
if (minBeds > 0) params.set("beds", String(minBeds))
window.history.replaceState(null, "", `?${params}`)
}
return (
<RealEstatePage
linkComponent={Link}
listings={listings}
neighborhoods={neighborhoods}
phone="(718) 555-0100"
mapBounds={{ north: 40.742, south: 40.66, west: -74.01, east: -73.918 }}
onSearch={syncUrl}
onFavoriteChange={(listing, favorite) => saveHome(listing.id, favorite)}
onValuation={requestValuation}
/>
)
}

Each listing is { id, mode, price, address, neighborhood, city, beds, baths, sqft, kind, color, lat, lng } plus the optional previousPrice, image, badge, href and favorite. Without an image the card draws a tinted illustration of the kind (brownstone, condo, loft, house, townhouse), which is handy while you wait for photos. Every string is in labels.

Replace the sample content before launch. The stats under the search ("1,200+ homes sold since 2009"), the agents, the testimonials and the footer's broker line all belong to the fictional Harbor & Pine agency. Pass your own stats (or stats={[]} to hide them), agents and testimonials, and set labels.brand and labels.legal. Sample reviews and sales numbers must never reach a live site.

Type Williamsburg into the search, open its pin on the map and press Estimate payment: the calculator below takes that home's price. Then switch to Rent and watch the homes, pins and price choices change
$ npx wingo-ui@latest add real-estate-page
ProReal Estate Page docs

How should the buy or rent search work?

The Buy / Rent switch changes everything that follows it: the featured homes, the map pins, the neighborhood medians (sale price or rent) and the price choices. Switching resets the price filter on purpose, since a $1M to $2M band means nothing for rentals. The location field matches the address, the neighborhood and the city as you type, so "11215", "Union Street" and "Park Slope" all work, and the count above the homes is a polite live region, so screen readers hear "4 homes for sale" without stealing focus.

The filtering runs on the listings you pass, fine for the 10 to 50 homes an agency features. onSearch fires on Search, on Show homes in the phone filters sheet and on a neighborhood tile, with { mode, location, price: [min, max], minBeds }. Use it to sync the URL or to send the visitor to a full results page. Only mode is controllable from outside, so for deep links that restore the whole search, build the results page from the Listing Grid.

What goes on a property listing card?

Good property listing page design puts the decision facts on the card so people tap fewer homes they would reject: the price (and the old price after a cut), beds, baths, area, the neighborhood and one short badge such as "Open house Sat". The template's card does that, with a heart that rolls back if onFavoriteChange rejects.

On a results page with real photos, use the Listing Card. It swipes through several photos on a phone (vertical scrolling still wins, and a swipe never opens the listing), shows the price per square foot under the amount, and has grid, list and compact layouts. A home maps onto its ListingData like this:

tsx
import { ListingCard, type ListingData } from "@/components/ui/listing-card"
const home: ListingData = {
id: "h-482",
title: "3-bed brownstone duplex with garden",
href: "/homes/h-482",
images: [
{ src: "/homes/h-482/front.jpg", alt: "The brownstone from Union Street" },
{ src: "/homes/h-482/kitchen.jpg", alt: "The kitchen" },
],
price: 1_895_000,
compareAtPrice: 1_950_000,
currency: "USD",
unit: { quantity: 1_840, unit: "sq ft" },
location: "Brooklyn, Park Slope",
attributes: ["3 beds", "2 baths", "1,840 sq ft"],
badges: [{ label: "Open house Sat", tone: "info" }],
}
export function HomeCard() {
return <ListingCard listing={home} imageAspect="16/10" size="lg" />
}

For rentals, add priceSuffix: "/mo". The 16/10 photo shape suits homes better than the default 4/3.

In the list layout the Park Slope apartment shows its price per square foot under the price. Swipe or drag its photos, then tap the heart to save it
$ npx wingo-ui@latest add listing-card
ProListing card docs

The Listing Grid wraps these cards for a results page: the count, a sort (a bottom sheet on phones), a grid / list switch it can remember per visitor, removable filter chips and loading more as you scroll.

How does the price pin map work, and when do you need a real map?

The template's map band shows every matching home as a pin with its compact price ("$1.25M", "$4,200") next to a list of the same homes. A pin or a row opens a preview card with View home and Estimate payment; Escape or the close button closes it and focus goes back to the pin. The map is drawn, not tiled: pins are placed by lat and lng inside the mapBounds you pass. That means no API key, no tile bill and no map library on a landing page, the right trade for one city's featured homes. It will not show the streets around a home.

For the street view of one home, on a listing detail page, use the Location Map. It is Leaflet with OpenStreetMap tiles, loads only when it nears the viewport, and falls back to a drawn card without network:

tsx
import { LocationMap } from "@/components/ui/location-map"
export function HomeLocation({ lat, lng }: { lat: number; lng: number }) {
return <LocationMap value={{ lat, lng }} readOnly defaultZoom={15} height={280} locale="en-US" />
}

Three caveats from its docs. OpenStreetMap's public tiles are meant for light use, so point tileUrl at your own provider in production. readOnly hides the search, but where an agent places a home on the map (an add listing form), the default offline search only knows Romanian towns: pass geocoder={nominatimGeocoder({ email, countryCodes: "us" })} or your own geocoder. And the line under the map names the place from that same Romanian list, so a US home shows only its coordinates there unless you pass reverseGeocoder={nominatimReverseGeocoder({ email })}. It holds one pin, so for a results map with hundreds of homes and clustering, use MapLibre or Leaflet with a cluster plugin directly. The Listing Detail Page block already puts a Location Map on the detail screen.

What happens on a phone?

Below 768px the template changes shape instead of shrinking:

  • The search becomes a stacked card: the Buy / Rent switch, the location field, then Filters and Search side by side. The price and beds selects are hidden.
  • Filters opens a bottom sheet with the Price Range (a histogram of the current homes over a two-thumb slider, Min and Max fields and preset chips) and a beds switch. Its button says "Show 4 homes" as you drag, so nobody applies a filter that returns nothing.
  • The featured homes become a snap rail: each card is 82% of the screen wide, so the next one peeks in and shows there is more to swipe.
  • In the map band, the list sits above the map, capped at 300px and scrolling on its own, with a 320px map under it.

That stacked map works for a dozen featured homes. On a results page with a long list it does not: the map ends up below a scroll the visitor never finishes. Show one view at a time instead, with a List / Map switch pinned near the thumb. Here is that pattern with the Listing Grid and the free Segmented Control; ResultsMap is your own map:

tsx
"use client"
import { useState } from "react"
import { List, Map as MapIcon } from "lucide-react"
import { ListingGrid } from "@/components/ui/listing-grid"
import type { ListingData } from "@/components/ui/listing-card"
import { SegmentedControl } from "@/components/ui/segmented-control"
import { ResultsMap } from "./results-map"
export function Results({ homes, total }: { homes: ListingData[]; total: number }) {
const [view, setView] = useState("list")
return (
<div className="md:grid md:grid-cols-2 md:gap-6">
<div className={view === "map" ? "max-md:hidden" : undefined}>
<ListingGrid items={homes} total={total} defaultView="list" imageAspect="16/10" columns={{ base: 1, xl: 2 }} />
</div>
<div className={view === "list" ? "max-md:hidden" : "max-md:h-[calc(100dvh-8rem)]"}>
<ResultsMap homes={homes} />
</div>
<div className="fixed inset-x-0 bottom-[max(1rem,env(safe-area-inset-bottom))] z-sticky flex justify-center md:hidden">
<SegmentedControl
aria-label="Show results as"
value={view}
onValueChange={setView}
options={[
{ value: "list", label: "List", icon: <List /> },
{ value: "map", label: "Map", icon: <MapIcon /> },
]}
className="shadow-floating"
/>
</div>
</div>
)
}

Both views stay mounted, so the map keeps its position and the list keeps its scroll when the visitor switches back. One catch: a map library measures its container, and a container hidden with display: none measures zero, so call map.resize() (MapLibre) or map.invalidateSize() (Leaflet) inside ResultsMap when the map view opens. From 768px the toggle disappears and the list and map sit side by side.

How does the mortgage estimate calculate the payment?

The calculator uses the standard amortization formula for principal and interest, then adds property tax (a yearly percent of the price), insurance and HOA fees per month. With the defaults (a $1,250,000 home, 20% down, 6.5% over 30 years, 0.9% tax and $150 insurance), the loan is $1,000,000, principal and interest come to $6,321, tax adds $938 and the estimate is $7,408 a month. The breakdown bar and totals update as the sliders move (arrow keys and Page Up / Down work too).

Set your market's averages through the mortgage prop, for example mortgage={{ taxRate: 1.1, insurance: 120, hoa: 350 }}; anything you leave out keeps its default. Estimate payment on a map preview fills the calculator with that home's price and scrolls to it. Keep the note that it is an estimate for planning only, and keep the wording away from anything that reads like a loan offer.

How do you wire the valuation form?

On a real estate landing page template, the valuation band is the lead capture for sellers. The form checks the address, the name and the email, focuses the first problem, and calls onValuation with { address, propertyType, beds, timeline, name, email, phone }. A returned promise keeps the button spinning; a rejection keeps what was typed and shows labels.failed. A server action fits:

ts
// app/actions.ts
"use server"
import type { RealEstateValuationValues } from "@/components/templates/real-estate-page"
import { db } from "@/lib/db"
export async function requestValuation(values: RealEstateValuationValues) {
if (!values.address?.trim() || !values.name?.trim() || !values.email?.includes("@")) {
throw new Error("invalid request")
}
await db.valuationRequest.create({ data: { ...values, createdAt: new Date() } })
}
export async function saveHome(listingId: string, favorite: boolean) {
// check the session here; a throw rolls the heart back
await db.savedHome.upsert({ listingId, favorite })
}

db stands for your own data layer. Validate again on the server, as here: a server action is a public endpoint, and the TypeScript types do not exist at runtime. Without onValuation the form pretends for 900 ms and confirms, which is fine for a preview and wrong for production.

Which parts are free and which are Pro?

The Real Estate Page, the Listing Card, the Listing Grid, the Location Map and the Price Range are Pro items. The Segmented Control is free, along with 74 other items of the 369 in the catalog. Pro is $8 a month, $80 a year or $150 once on the pricing page, and the 14-day refund applies while no Pro item has been downloaded. On a nextjs real estate website the template is usually the first screen, followed by a Listing Grid results page and a detail page. If your site is closer to a classifieds market, the React marketplace template tutorial builds those screens around a search URL.

More full screens are in the Build it category, and the Next.js admin dashboard tutorial covers the back office where agents would manage these listings.

Try it before you install it

Open the Real Estate Page template on your phone, switch to Rent, open Filters and drag the price, then tap a pin. If it fits your real estate website template react project, npx wingo-ui@latest add real-estate-page copies it into your app with a Pro account, ready for your listings, your bounds and your valuation action.

Components in this post

  • Real Estate Page

    A React real estate template: buy or rent search, listing cards, a price pin map, neighborhoods, a mortgage estimate and a valuation form.

    Pro
  • Listing card

    A React listing card for marketplaces: swipeable photos, price, place and time, badges and a heart, in grid, list and compact layouts.

    Pro
  • Listing grid

    A React listing grid for marketplace results: count, sort, view switch and filter chips over listing cards that load more as you scroll.

    Pro
  • Location Map

    A React location map to pick or show a place: a draggable pin, an optional radius with a towns count, offline search and a static fallback.

    Pro
  • Price range

    A React price range filter: a histogram of the results over a two-thumb slider, synced min and max inputs, preset chips and a live count.

    Pro

FAQ

Is there a free Next.js real estate template?

The two polished ones we checked on 2026-11-06 are paid: Homely costs $29 for a single use license on WrapPixel, and Sanity lists the Clay template as a paid template. The Wingo UI Real Estate Page is a Pro item, included in the $8 a month plan.

What pages does a real estate website need?

A home page with a buy or rent search, a results page with filters and a map, a listing detail page with photos, facts and the location, and a way for owners to request a valuation. Agent profiles, neighborhood guides and a mortgage estimate are what make buyers stay.

How should a property search map work on mobile?

Show either the list or the map, not both, and switch with a toggle that stays in reach of the thumb. Pins should show the price, and tapping one should open a short preview instead of leaving the results.

How is a monthly mortgage payment calculated?

Principal and interest use the standard amortization formula: loan times the monthly rate, divided by one minus (1 + monthly rate) to the power of minus the number of months. Property tax, insurance and HOA fees are then added per month.

Does the template need a Google Maps or Mapbox key?

No. The template draws its own map and places the pins from latitude and longitude inside the bounds you pass, so there is no key and no tile bill. The separate Location Map component uses Leaflet with OpenStreetMap tiles by default, and you point it at your own tile provider in production.

Try it in your project

Real Estate Page

Pro

A React real estate template: buy or rent search, listing cards, a price pin map, neighborhoods, a mortgage estimate and a valuation form.

75 components free, no account needed. Pro from $8 a month.

Open Real Estate Page liveSee pricing
  • Real estate
  • Next.js
  • Templates
  • Maps
  • Mobile UI

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Build it: screens and apps

Nextjs Restaurant Template: Open Now, Menu, Book a Table

A nextjs restaurant template built around three questions: are you open now, what is on the menu, can I book a table. Live hours, menu filters and booking.

SRSerban Rusu·Nov 6, 2026·10 min read
Build it: screens and apps

React Marketplace Template That Works Like an App

Build a react marketplace template on Next.js: a what and where search, facets in the URL, listing cards and a detail page that behave like an app on phones.

SRSerban Rusu·Oct 16, 2026·15 min read
Component guides

React Location Picker: Draggable Pin, Radius, Search

Build a React location picker on a real map: a draggable pin, a radius with a slider, address search, a phone sheet and a static card if the map fails.

SRSerban Rusu·Oct 19, 2026·9 min read
Newsletter

Get new posts by email

New posts, product news and occasional offers from Wingo UI. We count clicks to keep it relevant.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • For teams
  • Theme generator

Resources

  • Docs
  • Contact
  • Programs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Compare

  • Wingo UI vs paid kits
  • Wingo UI vs shadcn/ui
  • shadcn/ui alternatives
  • MUI alternatives
  • Tailwind Plus alternatives
  • Magic UI alternatives
  • Free components

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI