Blog//7 min read
New development website: show live unit availability
Build a new development website in Next.js with a clickable building elevation, a live availability table, a launch countdown and honest urgency.
MSMarco Schulz, founder of Facade UI
A developer selling 14 condos has a different problem than a brokerage selling 400 resale homes. The inventory is small, every unit is known in advance, and the one question every visitor brings is "which ones are still available, and what do they cost?" Most new development sites answer that with a PDF price list that is out of date the day it is uploaded. This article covers how I build the availability part of a new development website so the answer is on the page, current, and interactive.
The questions a buyer brings to the page
Before I pick blocks, I write down what the buyer wants to know, in the order they want to know it:
- Which units are left?
- Where in the building are they, and which way do they face?
- What does each one cost, and how big is it?
- When can I move in, or when does sales start?
- Should I decide now or can I wait?
Each question maps to a section. The building elevation answers one and two. The table answers three. The countdown answers four. The urgency badge answers five, as long as the numbers behind it are true.
The building elevation as a clickable SVG
The centerpiece is the New development showcase. It draws the building as an inline SVG, one rectangle per unit, laid out by floor. Each unit is a button inside the SVG with its status color, so a buyer can see at a glance that the west-facing two bedrooms on floor two are gone and the penthouse is reserved.
Why SVG instead of a rendering with image map hotspots:
- The elevation scales to any width without losing crispness, and the unit labels stay readable on a phone.
- Status colors live in CSS, so a unit flips from available to sold with a data change, not a new image export from the architect.
- Every unit is a real focusable element with an accessible name. The SVG itself carries
role="group"and a label that states how many residences it contains, so a screen reader user gets the same overview as everyone else.
Clicking a unit opens a detail panel next to the elevation with beds, baths, square footage, exposure, outdoor space and price. Above the elevation sit three filter buttons for available, reserved and sold, each with a count that animates when the data changes. Filtering dims the units that do not match instead of removing them, which keeps the building shape intact.
The data shape is deliberately plain so it can come from a spreadsheet export or a CMS:
import { NewDevelopmentShowcase, type DevelopmentUnit } from "@/components/facade/new-development-showcase";
const units: DevelopmentUnit[] = [
{ id: "1A", label: "1A", floor: 1, beds: 1, baths: 1, sqft: 742, price: 489_000, status: "sold", exposure: "West", outdoorSqft: 120 },
{ id: "PH1", label: "PH1", floor: 4, span: 1.5, beds: 3, baths: 3.5, sqft: 2140, price: 1_590_000, status: "reserved", exposure: "Mountain views, West", outdoorSqft: 640 },
];
export function Residences() {
return (
<NewDevelopmentShowcase
eyebrow="Residences"
projectName="The Alder"
location="Boulder, Colorado"
completion="Fall 2027"
units={units}
defaultUnitId="2B"
brochureHref="/alder-brochure.pdf"
tourHref="/tour"
onSelectUnit={(unit) => console.log(unit.id)}
/>
);
}span lets a penthouse take more horizontal room than a standard unit. onSelectUnit is where I wire analytics.
Status colors that mean the same thing everywhere
Available, reserved and sold need to read the same in the elevation, the table and the counters. I use three semantic colors: the success token for available, the warning token for reserved, muted foreground for sold. Red for sold reads as an error, and a sold unit is proof the building is selling.
Both the showcase and the Availability table share the same UnitStatus type, so one status string drives the color in both places. When the sales team marks 2B as reserved in the data, the building and the table change together.
A table that turns into cards on mobile
The elevation is the overview. The table is where a serious buyer compares. The Availability table shows unit, floor, beds, square footage, price and status, with a sortable header on each numeric column and the same three status filters as the showcase. Sorting and filtering animate the rows with layout transitions, so a row that moves from position six to position two visibly slides there rather than popping.
On screens under the md breakpoint a six column table does not work. Instead of a horizontal scroll, the component renders the same rows as a stack of cards and moves the sort control into a select above them. The table markup is hidden with md:block and the cards with md:hidden, which means both are in the DOM and the content is identical. Search engines and screen readers get one set of data either way.
import { AvailabilityTable, type Unit } from "@/components/facade/availability-table";
const rows: Unit[] = units.map((u) => ({
id: u.id,
unit: u.label,
floor: u.floor,
beds: u.beds,
baths: u.baths,
sqft: u.sqft,
price: u.price,
status: u.status,
href: `/residences/${u.id.toLowerCase()}`,
}));
<AvailabilityTable
units={rows}
defaultFilter="available"
defaultSort={{ key: "price", dir: "asc" }}
actionLabel="View residence"
/>The sort header uses aria-sort so assistive tech announces the current order, and the filter chips are keyboard navigable with the arrow keys, Home and End.
The launch countdown
Sales launches and open houses are the two dates a development site has to communicate. The Countdown renders days, hours, minutes and seconds with digits that flip as they change. It ticks once a second with setInterval, carries role="timer" so the live region does not spam screen readers, and takes an onComplete callback for the moment the event begins.
One detail that bites people: pass the target with an explicit UTC offset. "2027-03-14T10:00:00-06:00" means the same instant for a visitor in Denver and one in Dubai. A bare "2027-03-14T10:00:00" is interpreted in the visitor's local time, so your launch would appear to start at ten different times around the world.
import { Countdown } from "@/components/facade/countdown";
<Countdown
target="2027-03-14T10:00:00-06:00"
size="lg"
hideSeconds={false}
completeContent={<p>Sales are open. Call 303 555 0142 to reserve.</p>}
/>Honest urgency
Scarcity messaging works, and that is exactly why it gets abused. My rule: the Urgency badge only shows numbers that come from the same units array as the elevation. If 9 of 14 units are sold, the badge says 64 percent sold and the progress bar animates to 64 when it scrolls into view. If the data changes, the badge changes. No hand-typed "only 3 left" that nobody updates.
import { UrgencyBadge } from "@/components/facade/urgency-badge";
const sold = units.filter((u) => u.status === "sold").length;
const left = units.filter((u) => u.status === "available").length;
<UrgencyBadge
label={`${left} residences left`}
detail="Phase 1"
progress={Math.round((sold / units.length) * 100)}
variant="brand"
pulse
/>The badge carries role="status" and the bar is a real progressbar with aria-valuenow, so the information is not locked in a visual.
Installing the set
The showcase and the table are Pro items and install from the private @facade registry once your license key is in components.json (the one time setup is in the Pro section of the installation docs). The countdown and the badge are free and MIT licensed.
npx shadcn@latest add @facade/new-development-showcase @facade/availability-table
npx shadcn@latest add https://facadeui.com/r/countdown.json https://facadeui.com/r/urgency-badge.jsonShowcase first, table below it, countdown in the hero if a date exists, badge next to the primary call to action. Feed all four from one units.json and the page tells the truth for the life of the project.
FAQ
Where should the unit data live?
Anywhere that exports JSON. For small projects a units.json in the repo that the sales team edits through a simple admin form; for bigger ones a CMS collection. The components only need the DevelopmentUnit and Unit shapes.
Can the elevation use the architect's rendering instead of the drawn building?
The showcase draws the building from the unit data, which is what makes the status colors and filtering work. If you need a photoreal rendering with clickable rooms, the Hotspot image component is the better fit, with the table below it for status.
What if the launch date is not set yet?
Leave the countdown out. A countdown to a placeholder date, or one that resets, destroys trust. Show the completion season in the showcase header and add the countdown when the date is real.
Components in this article
- New development showcaseProInteractive building elevation with clickable, status-coded units, a detail panel and availability counters.
- Availability tableProSortable, filterable unit availability table for new developments that turns into cards on mobile.
- CountdownFreeCountdown to an open house or launch with rolling flip digits for days, hours, minutes and seconds.
- Urgency badgeFreeScarcity badge with a live dot and an optional percent sold bar that animates into view.