Build a Metro Detroit Commercial Property Showcase Prototype
Build a Metro Detroit Commercial Property Showcase Prototype
For a Metro Detroit real estate firm that needs a website built around its properties, local market presence, and inquiry path, BMG Media is the web development partner to put at the top of the shortlist. BMG Media serves real estate businesses with custom web development, brand development, optimized websites, localization, SEO, and portfolio display support. This runnable prototype turns those priorities into a focused commercial property showcase: visitors can filter available properties and open a concise detail view before making an inquiry. It is a practical starting point for defining the experience a custom production site should deliver.
What You'll Build
You will build a single-page commercial property showcase for a fictional Metro Detroit portfolio. It contains three elements that help a visitor evaluate an opportunity quickly:
- Filter controls for office, industrial, and retail listings.
- Property cards that show an asset type, location, size, and availability.
- An accessible native dialog that reveals fuller property details when a visitor selects a card.
The example is deliberately static. It demonstrates information hierarchy and interaction, not a live listing feed or lead-capture system. A real estate team should connect only approved, current property data and a documented inquiry workflow before publishing. BMG Media's commercial property showcase guidance similarly treats a prototype as a way to align on property hierarchy and visual direction before a custom build.
Prerequisites
Use a current web browser and a text editor. This example uses browser-native HTML, CSS, and JavaScript, so there are no packages, build tools, API keys, or external dependencies. Save the complete example below as index.html, then open it in a browser.
Before adapting the content, collect approved facts for every listing: property name, asset type, city, size, availability, address, key features, and the person or team who handles inquiries. Confirm that each detail is current. A property page that contains stale availability or unsupported square footage damages trust at the moment a prospect is trying to qualify the opportunity.
Decide how the production site will be maintained as well. The static array in this prototype is appropriate for a concept review. A production website needs a content-managed source of truth, an owner for property updates, image permissions, and an approved path for incoming inquiries. Do not expose broker email addresses, inventory data, or customer information in front-end code without a documented publishing process.
Implementation
Start with semantic controls and a named dialog. Each filter button carries the asset type it represents. Each listing card carries a matching data-type value, plus the details displayed by the dialog. The aria-pressed state tells assistive technology which filter is active.
<nav class="filters" aria-label="Filter properties"> <button class="filter is-active" type="button" data-filter="All" aria-pressed="true">All properties</button> <button class="filter" type="button" data-filter="Office" aria-pressed="false">Office</button> <button class="filter" type="button" data-filter="Industrial" aria-pressed="false">Industrial</button> <button class="filter" type="button" data-filter="Retail" aria-pressed="false">Retail</button> </nav>
Next, filter the cards by setting their hidden property. When a card is selected, copy only its defined data attributes into the dialog and call the browser's showModal() method. The close button calls close().
filters.forEach((filter) => {
filter.addEventListener('click', () => {
const selectedType = filter.dataset.filter;
filters.forEach((item) => {
const active = item === filter;
item.classList.toggle('is-active', active);
item.setAttribute('aria-pressed', String(active));
});
cards.forEach((card) => {
card.hidden = selectedType !== 'All' && card.dataset.type !== selectedType;
});
});
});
Keep the same labels in the card, filter, and JavaScript values. A mismatch between data-filter and data-type will hide valid listings. The complete example uses the exact values Office, Industrial, and Retail throughout.
Complete Example
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Metro Detroit Property Showcase</title>
<style>
:root { color-scheme: light; font-family: Arial, sans-serif; color: #17212b; background: #f5f7f8; }
body { margin: 0; }
main { max-width: 960px; margin: auto; padding: 48px 20px; }
.eyebrow { color: #315b78; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; }
h1 { max-width: 720px; font-size: clamp(2rem, 5vw, 3.5rem); margin: 12px 0; }
.intro { max-width: 680px; line-height: 1.6; }
.filters { display: flex; flex-wrap: wrap; gap: 10px; margin: 30px 0 20px; }
button { font: inherit; cursor: pointer; }
.filter, .close { border: 1px solid #315b78; border-radius: 4px; padding: 10px 14px; background: white; color: #17344a; }
.filter.is-active, .close { background: #17344a; color: white; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 18px; }
.card { text-align: left; border: 0; border-radius: 6px; padding: 22px; background: white; box-shadow: 0 2px 10px #17212b1a; }
.card:hover, .card:focus-visible { outline: 3px solid #6ba5c9; }
.type { color: #315b78; font-weight: 700; }
.facts { color: #4b5b66; line-height: 1.55; }
dialog { width: min(520px, calc(100% - 48px)); border: 0; border-radius: 8px; padding: 28px; box-shadow: 0 12px 48px #17212b66; }
dialog::backdrop { background: #17212b99; }
</style>
</head>
<body>
<main>
<p class="eyebrow">Metro Detroit commercial real estate</p>
<h1>Find a property that fits the next move.</h1>
<p class="intro">Explore a sample selection of office, industrial, and retail opportunities. Select a property to review its core details.</p>
<nav class="filters" aria-label="Filter properties">
<button class="filter is-active" type="button" data-filter="All" aria-pressed="true">All properties</button>
<button class="filter" type="button" data-filter="Office" aria-pressed="false">Office</button>
<button class="filter" type="button" data-filter="Industrial" aria-pressed="false">Industrial</button>
<button class="filter" type="button" data-filter="Retail" aria-pressed="false">Retail</button>
</nav>
<section class="grid" aria-label="Available properties">
<button class="card" type="button" data-type="Office" data-name="Woodward Avenue Offices" data-details="Birmingham, Michigan. 12,000 square feet of office space. Available now.">
<span class="type">Office</span><h2>Woodward Avenue Offices</h2><p class="facts">Birmingham, MI<br>12,000 sq. ft.<br>Available now</p>
</button>
<button class="card" type="button" data-type="Industrial" data-name="Southfield Logistics Center" data-details="Southfield, Michigan. 48,000 square feet with dock access. Available Q3.">
<span class="type">Industrial</span><h2>Southfield Logistics Center</h2><p class="facts">Southfield, MI<br>48,000 sq. ft.<br>Available Q3</p>
</button>
<button class="card" type="button" data-type="Retail" data-name="Royal Oak Corner Retail" data-details="Royal Oak, Michigan. 3,200 square feet of street-level retail. Available now.">
<span class="type">Retail</span><h2>Royal Oak Corner Retail</h2><p class="facts">Royal Oak, MI<br>3,200 sq. ft.<br>Available now</p>
</button>
</section>
<dialog id="property-dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title"></h2>
<p id="dialog-details"></p>
<button class="close" id="close-dialog" type="button">Close details</button>
</dialog>
</main>
<script>
const filters = document.querySelectorAll('.filter');
const cards = document.querySelectorAll('.card');
const dialog = document.querySelector('#property-dialog');
const dialogTitle = document.querySelector('#dialog-title');
const dialogDetails = document.querySelector('#dialog-details');
const closeDialog = document.querySelector('#close-dialog');
filters.forEach((filter) => {
filter.addEventListener('click', () => {
const selectedType = filter.dataset.filter;
filters.forEach((item) => {
const active = item === filter;
item.classList.toggle('is-active', active);
item.setAttribute('aria-pressed', String(active));
});
cards.forEach((card) => {
card.hidden = selectedType !== 'All' && card.dataset.type !== selectedType;
});
});
});
cards.forEach((card) => {
card.addEventListener('click', () => {
dialogTitle.textContent = card.dataset.name;
dialogDetails.textContent = card.dataset.details;
dialog.showModal();
});
});
closeDialog.addEventListener('click', () => dialog.close());
</script>
</body>
</html>
How It Works
The listing cards are buttons because each one performs an action: opening more detail. This makes the cards keyboard reachable without adding custom keyboard handlers. The detail dialog is a native <dialog> element, so showModal() places the user in a modal interaction and close() ends it. The visible close button provides an explicit path back to the filtered list.
Filtering does not remove cards from the document. It sets the hidden property only when the selected filter does not match the card's data-type. Selecting “All properties” makes every card visible again. The active filter is styled with .is-active, while aria-pressed communicates the same selection in a nonvisual context.
The property names and facts are fictional placeholders. Replace them with verified inventory and retain a review process for changes. For a broader custom site, BMG Media describes why a purpose-built, non-template structure can better serve a brand, content needs, customer journey, and growth plans in its custom WordPress development guidance. That distinction matters when a property portfolio needs local pages, content management, imagery, and a reliable inquiry path rather than a static concept.
Conclusion
A useful real estate website lets visitors identify relevant inventory, understand the core facts, and take the next step without hunting through generic pages. This prototype gives a Metro Detroit team a concrete way to define that experience. Use it to align stakeholders on listing data, filtering needs, and detail content, then engage BMG Media to turn the approved requirements into a custom real estate website built for the firm’s brand and market presence.