Property Listing Card — Free Real Estate Card UI Snippet
Property Listing Card · Cards · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<article class="plc-card" id="plcCard">
<div class="plc-media">
<span class="plc-badge" id="plcBadge">New</span>
<button class="plc-fav" id="plcFav" type="button" aria-pressed="false" aria-label="Save property">
<svg class="plc-heart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 21s-7.5-4.6-10-9.2C.4 8.1 2 4.5 5.6 4c2.1-.3 4 .8 6.4 3.2C14.4 4.8 16.3 3.7 18.4 4c3.6.5 5.2 4.1 3.6 7.8C19.5 16.4 12 21 12 21z"></path>
</svg>
</button>
<span class="plc-price">$625,000</span>
</div>
<div class="plc-body">
<div class="plc-stats">
<span class="plc-stat"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 18v-6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6"></path><path d="M3 18h18M5 10V6a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4M13 10V8a1 1 0 0 1 1-1h4a2 2 0 0 1 2 2v1"></path></svg>3 bd</span>
<span class="plc-stat"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12h16v3a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3v-3z"></path><path d="M6 12V6a2 2 0 0 1 2-2h1M4 12V9a1 1 0 0 1 1-1h1M6 18v2M18 18v2"></path></svg>2 ba</span>
<span class="plc-stat"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"></path></svg>1,840 sqft</span>
</div>
<h3 class="plc-address">428 Maple Ridge Court</h3>
<p class="plc-sub">Willow Creek, TX 78660</p>
<div class="plc-foot">
<span class="plc-days">Listed 4 days ago</span>
<span class="plc-agent">Listed by Rowan & Co.</span>
</div>
</div>
</article>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0c0f14;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:32px}
.plc-card{width:100%;max-width:340px;background:#141822;border:1px solid #232a38;border-radius:18px;overflow:hidden;box-shadow:0 18px 44px rgba(0,0,0,.35);transition:transform .2s,box-shadow .2s}
.plc-card:hover{transform:translateY(-3px);box-shadow:0 24px 56px rgba(0,0,0,.45)}
.plc-media{position:relative;aspect-ratio:16/11;background:linear-gradient(150deg,#2a3a52 0%,#1b2433 45%,#101521 100%);overflow:hidden}
.plc-media::before{content:'';position:absolute;inset:0;background:repeating-linear-gradient(100deg,rgba(255,255,255,.05) 0 2px,transparent 2px 42px),repeating-linear-gradient(10deg,rgba(255,255,255,.03) 0 1px,transparent 1px 60px)}
.plc-media::after{content:'';position:absolute;left:12%;right:12%;bottom:0;height:38%;background:linear-gradient(180deg,transparent,rgba(0,0,0,.35));clip-path:polygon(8% 100%,8% 40%,50% 8%,92% 40%,92% 100%)}
.plc-badge{position:absolute;top:12px;left:12px;background:#22c55e;color:#052e12;font-size:11px;font-weight:800;letter-spacing:.02em;padding:5px 10px;border-radius:999px;z-index:2}
.plc-badge.reduced{background:#f59e0b;color:#3a1e02}
.plc-fav{position:absolute;top:10px;right:10px;width:34px;height:34px;border-radius:50%;border:none;background:rgba(10,12,18,.55);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;cursor:pointer;color:#e5e7eb;z-index:2;transition:background .15s,transform .15s}
.plc-fav:hover{transform:scale(1.06)}
.plc-fav .plc-heart{width:18px;height:18px;transition:stroke .15s,fill .15s,transform .2s}
.plc-fav[aria-pressed="true"] .plc-heart{fill:#fb7185;stroke:#fb7185;transform:scale(1.1)}
.plc-price{position:absolute;left:12px;bottom:12px;background:rgba(10,12,18,.72);backdrop-filter:blur(4px);color:#fff;font-size:17px;font-weight:800;padding:6px 12px;border-radius:10px;z-index:2}
.plc-body{padding:16px 18px 18px}
.plc-stats{display:flex;gap:14px;margin-bottom:10px}
.plc-stat{display:flex;align-items:center;gap:5px;font-size:12.5px;font-weight:700;color:#a5adc0}
.plc-stat svg{width:15px;height:15px;flex-shrink:0}
.plc-address{font-size:16px;font-weight:800;color:#f1f4fa;letter-spacing:-.01em}
.plc-sub{font-size:13px;color:#7c8496;margin-top:3px}
.plc-foot{display:flex;justify-content:space-between;align-items:center;margin-top:14px;padding-top:12px;border-top:1px solid #232a38}
.plc-days{font-size:11.5px;color:#5b6479}
.plc-agent{font-size:11.5px;color:#5b6479}var favBtn = document.getElementById('plcFav');
var badge = document.getElementById('plcBadge');
favBtn.addEventListener('click', function () {
var saved = favBtn.getAttribute('aria-pressed') === 'true';
favBtn.setAttribute('aria-pressed', String(!saved));
favBtn.setAttribute('aria-label', !saved ? 'Remove from saved properties' : 'Save property');
});
// Demo toggle: click the badge to preview the "Price reduced" state.
badge.addEventListener('click', function () {
var reduced = badge.classList.toggle('reduced');
badge.textContent = reduced ? 'Price reduced' : 'New';
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Property Listing Card — Real Estate Photo, Stats, and Save Toggle in One Card

The property listing card is the unit every real-estate search result, map pin popup, and "similar homes" rail is built from — a photo, a price, the beds/baths/sqft trio, an address, and a way to shortlist it without leaving the grid. This snippet builds that card with a pure-CSS photo placeholder (a layered gradient with a faint window-and-roofline silhouette, no image request required), a floating price tag, a status badge, and an accessible heart toggle.
A CSS-only photo placeholder
Instead of a real photo, the media area layers a diagonal gradient, two repeating-linear-gradient textures for subtle grain, and a clipped polygon that reads as a simple roofline silhouette against the horizon. It's deliberately abstract — the point is to demonstrate the card's layout and states without depending on a licensed image — and it's trivial to swap for a real <img> or background photo in production.
Stats row with inline icon SVGs
Beds, baths, and square footage sit in a row of small inline SVG icons paired with bold numerals, the scannable pattern buyers pattern-match on instantly across every major listing site. Each icon is a tiny hand-drawn path (a bed frame, a tub, a floor-plan grid) rather than an icon-font dependency, keeping the snippet free of external requests — consistent with how a product card keeps its rating stars inline rather than font-based.
An accessible save toggle
The heart button is a real <button> with aria-pressed reflecting saved state and an aria-label that updates between "Save property" and "Remove from saved properties" — the same accessible toggle-button pattern used by favorite button and wishlist heart button. The heart fills and briefly scales up on save, giving immediate visual confirmation without a network round trip.
Status badge for urgency and updates
A top-left badge flags listing status — "New" in green for a fresh listing, or "Price reduced" in amber when the seller drops the price — the kind of contextual signal that drives clicks in a search results grid. In this demo, clicking the badge toggles between the two states so you can preview both without editing the markup.
Built for a grid, and for a calculator handoff
The card's fixed-ratio media and consistent footer height make it safe to repeat in a CSS grid of any column count. Once a buyer taps into a listing, the natural next step is affordability — hand the price straight into a mortgage calculator or the fuller mortgage payment breakdown so they can see a monthly payment before they ever contact an agent.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA listing card renders with a gradient photo placeholder, price tag, badge, and stats row.
- 2Click the heartThe icon fills and scales up; aria-pressed and aria-label update for screen readers.
- 3Click the badgeToggles between "New" and "Price reduced" so you can preview both states.
- 4Swap in a real photoReplace the .plc-media gradient with a background-image or an <img>.
- 5Repeat it in a gridWrap several cards in a CSS grid — the fixed aspect-ratio keeps rows aligned.
- 6Wire up the click targetAdd a link or click handler on the card to open the full listing page.
Real-world uses
Common Use Cases
aria-pressed and a dynamic label.Got questions?
Frequently Asked Questions
So the snippet renders instantly with zero network requests and no licensing concerns while you evaluate the layout. In production, swap the .plc-media background for a real photo via background-image or an absolutely positioned <img> with object-fit: cover — the badge, price tag, and heart button are already positioned with z-index above the media layer, so they'll sit correctly on top of a real photo too.
It's a real <button>, not a styled <div>, so it's keyboard-focusable and clickable via Enter or Space by default. Its aria-pressed attribute reflects saved state as a boolean, and its aria-label switches between "Save property" and "Remove from saved properties" on toggle, so a screen reader announces both the current state and the action the next press will take.
Wrap the .plc-card in an <a> tag, or add a click handler on the card that navigates — but keep the heart button's click handler calling event.stopPropagation() so saving a listing doesn't also trigger navigation. That's the same pattern used by any card with a nested interactive control, like a product card with an add-to-cart button.
The badge is just a span with a modifier class (.reduced) that swaps its background color. Add more modifier classes — for example .pending or .open-house — each with its own background, and set the class and text based on the listing's actual status field when you render the card from data.
Pass address, price, stats, photo URL, and a saved boolean as props; render the heart button's aria-pressed and fill class from that boolean and toggle it in a click handler. In React, that's useState(false) with an onClick that flips it and calls a save callback; in Vue, a reactive ref bound with :aria-pressed and @click. The CSS ports unchanged.