Get the code for a free, modern real estate website template homepage. Built with HTML, Tailwind CSS, and JavaScript.
Table of Contents
Want to build a stunning, professional website for a real estate business? Starting from scratch can be tough. Many templates are old, hard to use, or expensive. That's why I'm sharing this free real estate website template for a modern homepage.
This template is perfect for developers, freelancers, or anyone who wants a high-quality starting point. It's built with modern tools, it's easy to customize, and it's free!
Prerequisites
Before we start, make sure you have a basic understanding of these technologies. You don't need to be an expert, but being familiar helps.
- HTML: For the website structure.
- Tailwind CSS: For all the styling (no separate, messy CSS files!).
- JavaScript: For the interactive parts.
- A Code Editor: Like VS Code (it's free).
This template also uses a few popular, lightweight plugins to make it look amazing:
- AOS (Animate On Scroll): For cool fade-in and slide-in animations.
- Swiper.js: For a beautiful, touch-friendly image slider.
- Lucide Icons: For clean and simple icons.
Step-by-Step: Building Your Real Estate Homepage
We'll build this in three simple steps: setting up the HTML structure, adding the styling (via Tailwind and plugins), and finally, adding the JavaScript to make it all work.
Source Code
Step 1 (HTML Code):
First, we need the HTML. This is the skeleton of our homepage. I've structured it with clear sections: a navigation bar, a hero section (the big image at the top), a "Featured Listings" area, and a footer.
We will also add the links to Tailwind CSS, AOS, Swiper, and Lucide Icons in the <head> section. This is the easiest way to get started without a complex build process.
Step 2 (CSS Code):
The best part about this template is that it's about 99% styled with Tailwind CSS.
All the main styling—like buttons, cards, layouts, and fonts—is done with Tailwind's utility classes directly in the HTML. This is why it's so fast to build.
However, for a few complex components, it's cleaner to use a small, custom CSS file. We will use a style.css file only for these specific parts:
- The Navbar (for things like sticky behaviour or complex dropdowns)
- The Swiper.js Slider (to override default plugin styles)
- A Scroll-to-Top Button
- A Chatbot Widget
This "hybrid approach" gives you the speed of Tailwind for the whole site, plus the fine-tuned control of custom CSS where it matters most.
body {
font-family: 'Poppins', sans-serif;
background-color: #FDFDFB;
color: #1A1A1A;
overflow-x: hidden;
}
h1,
h2,
h3,
h4,
h5,
h6,
.font-display {
font-family: 'Playfair Display', serif;
}
/* --- Header Styles --- */
header.scrolled {
background-color: rgba(253, 253, 251, 0.85);
backdrop-filter: blur(10px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
/* --- Mobile Nav Overlay --- */
.nav-overlay {
transform: translateY(-100%);
transition: transform 0.6s cubic-bezier(0.76, 0, 0.24, 1);
}
.nav-overlay.open {
transform: translateY(0);
}
.nav-overlay a {
transition: color 0.3s, letter-spacing 0.3s;
}
.nav-overlay a:hover {
color: #C0A067;
letter-spacing: 0.1em;
}
/* --- Hamburger Menu Icon --- */
.hamburger span {
display: block;
width: 28px;
height: 2px;
margin: 6px 0;
transition: all 0.4s cubic-bezier(0.76, 0, 0.24, 1);
}
.hamburger.open span:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.open span:nth-child(2) {
opacity: 0;
transform: scale(0.1);
}
.hamburger.open span:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
/* --- Scroll Progress Indicator --- */
#scroll-indicator {
position: fixed;
top: 0;
left: 0;
height: 3px;
background-color: #C0A067;
z-index: 100;
transition: width 0.1s ease-out;
}
/* --- Testimonial Slider Styles --- */
.testimonial-slider .swiper-slide {
padding: 2rem;
background-color: #FDFDFB;
border-radius: 0.75rem;
border: 1px solid rgba(26, 26, 26, 0.1);
transition: all 0.3s ease;
}
.testimonial-slider .swiper-pagination-bullet {
background-color: #C0A067;
width: 10px;
height: 10px;
opacity: 0.5;
transition: all 0.3s ease;
}
.testimonial-slider .swiper-pagination-bullet-active {
opacity: 1;
width: 30px;
border-radius: 5px;
}
/* Scroll to Top */
#scrollTopBtn {
position: fixed;
bottom: 2.5rem;
right: 2.5rem;
width: 3.5rem;
height: 3.5rem;
border-radius: 9999px;
background-color: rgba(253, 253, 251, 0.8);
backdrop-filter: blur(8px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
/* Hide by default */
opacity: 0;
visibility: hidden;
transform: translateY(20px);
transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
#scrollTopBtn.is-visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
#scrollTopBtn:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
transform: translateY(-4px);
}
#progress-ring {
transform: rotate(-90deg);
transform-origin: 50% 50%;
transition: stroke-dashoffset 0.1s linear;
}
/* Chatbot UI */
#chat-toggle-btn {
position: fixed;
bottom: 2.5rem;
left: 2.5rem;
width: 3.75rem;
height: 3.75rem;
border-radius: 9999px;
background-color: #1A1A1A;
color: #FDFDFB;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
visibility: hidden;
transform: translateY(20px) scale(0.9);
animation: fadeIn 0.5s 1s cubic-bezier(0.25, 1, 0.5, 1) forwards;
transition: all 0.4s cubic-bezier(0.25, 1, 0.5, 1);
}
#chat-toggle-btn:hover {
background-color: #C0A067;
color: #1A1A1A;
transform: translateY(-4px) scale(1.05);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* Fade in animation for chat button */
@keyframes fadeIn {
to {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
}
#chatbot-window {
position: fixed;
bottom: 7.5rem;
left: 2.5rem;
width: 360px;
max-width: calc(100vw - 5rem);
height: 500px;
max-height: calc(100vh - 10rem);
background-color: #FDFDFB;
border: 1px solid #F5F5F5;
border-radius: 1rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
z-index: 99;
display: flex;
flex-direction: column;
overflow: hidden;
/* Hide by default */
opacity: 0;
visibility: hidden;
transform: translateY(30px) scale(0.95);
transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}
#chatbot-window.is-visible {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
/* Chatbot Header */
.chatbot-header {
background-color: #1A1A1A;
color: #FDFDFB;
padding: 1rem 1.5rem;
border-bottom: 1px solid #C0A067;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
/* Chatbot Messages */
.chatbot-messages {
flex-grow: 1;
padding: 1.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.chat-message {
padding: 0.75rem 1.25rem;
border-radius: 1.25rem;
max-width: 80%;
line-height: 1.6;
}
.chat-message.bot {
background-color: #F5F5F5;
color: #1A1A1A;
align-self: flex-start;
border-bottom-left-radius: 0.25rem;
}
.chat-message.user {
background-color: #C0A067;
color: #1A1A1A;
align-self: flex-end;
border-bottom-right-radius: 0.25rem;
}
/* Chatbot Input */
.chatbot-input {
padding: 1rem 1.5rem;
border-top: 1px solid #F5F5F5;
background-color: #FDFDFB;
flex-shrink: 0;
}
.chatbot-input form {
display: flex;
gap: 0.5rem;
}
.chatbot-input input {
flex-grow: 1;
border: 1px solid #F5F5F5;
background: #F5F5F5;
border-radius: 9999px;
padding: 0.75rem 1.25rem;
color: #1A1A1A;
transition: all 0.3s;
}
.chatbot-input input:focus {
background: #FDFDFB;
border-color: #C0A067;
box-shadow: 0 0 0 2px rgba(192, 160, 103, 0.3);
outline: none;
}
.chatbot-input button {
width: 3rem;
height: 3rem;
border-radius: 9999px;
background-color: #1A1A1A;
color: #FDFDFB;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.3s;
}
.chatbot-input button:hover {
background-color: #C0A067;
color: #1A1A1A;
} Step 3 (JavaScript Code):
Finally, we need to make our interactive elements work. This template uses JavaScript for three things:
- Initializing the Swiper.js slider for our featured listings.
- Initializing the AOS library for our scroll animations.
- Initializing the Lucide Icons to make them appear.
Create a new file named script.js and add the following code:
// Custom Tailwind Configuration
tailwind.config = {
theme: {
extend: {
colors: {
'ivory': '#FDFDFB', // Main background
'charcoal': '#1A1A1A', // Deep text, dark backgrounds
'soft-gold': '#C0A067', // Accents, buttons, borders
'dark-gold': '#A78B58', // Hover for gold
'light-gray': '#F5F5F5', // Secondary backgrounds
},
fontFamily: {
'sans': ['Poppins', 'sans-serif'],
'display': ['Playfair Display', 'serif'],
}
}
},
}
document.addEventListener('DOMContentLoaded', function () {
// --- Initialize AOS (Animate on Scroll) ---
AOS.init({
duration: 1000,
once: true,
offset: 50,
});
// --- Initialize Lucide Icons ---
if (typeof lucide !== 'undefined') {
lucide.createIcons();
} else {
console.error('Lucide icons script not loaded or failed to load.');
}
// --- Set Current Year in Footer ---
document.getElementById('current-year').textContent = new Date().getFullYear();
// --- Header Scroll Effect ---
const header = document.getElementById('navbar');
const logo = document.getElementById('logo');
const hamburgerBtn = document.getElementById('hamburger-btn');
const desktopNavLinks = document.querySelectorAll('.nav-link-desktop');
const desktopNavCta = document.getElementById('nav-cta-desktop');
const handleScroll = () => {
const isScrolled = window.scrollY > 50;
const isOpen = hamburgerBtn.classList.contains('open');
if (isScrolled) {
header.classList.add('scrolled');
logo.classList.remove('text-ivory');
logo.classList.add('text-charcoal');
desktopNavLinks.forEach(link => {
link.classList.remove('text-ivory/80');
link.classList.add('text-charcoal/80');
});
desktopNavCta.classList.remove('bg-ivory', 'text-charcoal');
desktopNavCta.classList.add('bg-charcoal', 'text-ivory');
} else {
header.classList.remove('scrolled');
logo.classList.add('text-ivory');
logo.classList.remove('text-charcoal');
desktopNavLinks.forEach(link => {
link.classList.add('text-ivory/80');
link.classList.remove('text-charcoal/80');
});
desktopNavCta.classList.add('bg-ivory', 'text-charcoal');
desktopNavCta.classList.remove('bg-charcoal', 'text-ivory');
}
// Handle hamburger icon color
if (!isOpen) {
hamburgerBtn.querySelectorAll('span').forEach(span => {
if (isScrolled) {
span.classList.remove('bg-ivory');
span.classList.add('bg-charcoal');
} else {
span.classList.add('bg-ivory');
span.classList.remove('bg-charcoal');
}
});
}
};
window.addEventListener('scroll', handleScroll);
handleScroll(); // Run on load to set initial state
// --- Full-screen Nav Toggle ---
const navOverlay = document.getElementById('nav-overlay');
hamburgerBtn.addEventListener('click', () => {
const isOpen = hamburgerBtn.classList.toggle('open');
navOverlay.classList.toggle('open');
document.body.classList.toggle('overflow-hidden');
// On toggle, force hamburger color
hamburgerBtn.querySelectorAll('span').forEach(span => {
if (isOpen) {
span.classList.remove('bg-charcoal');
span.classList.add('bg-ivory');
} else {
// On close, re-run scroll check to set correct color
handleScroll();
}
});
});
// Close overlay when a link is clicked
navOverlay.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
hamburgerBtn.classList.remove('open');
navOverlay.classList.remove('open');
document.body.classList.remove('overflow-hidden');
handleScroll();
});
});
// --- Scroll Indicator ---
const scrollIndicator = document.getElementById('scroll-indicator');
window.addEventListener('scroll', () => {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
if (scrollHeight > clientHeight) {
const scrollPercent = (scrollTop / (scrollHeight - clientHeight)) * 100;
scrollIndicator.style.width = `${scrollPercent}%`;
}
});
// --- Number Counter for Statistics ---
const counters = [
{ id: 'stat-1', target: 12, duration: 2000 },
{ id: 'stat-2', target: 25, duration: 2000 },
{ id: 'stat-3', target: 98, duration: 2000 }
];
const animateCounter = (el, target, duration) => {
let start = 0;
let startTime = null;
const step = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
const percentage = Math.min(progress / duration, 1);
start = Math.floor(percentage * target);
el.textContent = start;
if (percentage < 1) {
window.requestAnimationFrame(step);
} else {
el.textContent = target; // Ensure it ends on the exact target
}
};
window.requestAnimationFrame(step);
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
counters.forEach(counter => {
const el = document.getElementById(counter.id);
if (el && !el.dataset.animated) {
animateCounter(el, counter.target, counter.duration);
el.dataset.animated = true; // Mark as animated
}
});
observer.disconnect(); // Stop observing once animated
}
});
}, { threshold: 0.5 }); // Trigger when 50% visible
const statSection = document.querySelector('#stat-1'); // Observe the first stat
if (statSection) {
observer.observe(statSection.parentElement);
}
// --- Testimonials Slider ---
if (typeof Swiper !== 'undefined') {
new Swiper('.testimonial-slider', {
loop: true,
slidesPerView: 1,
spaceBetween: 30,
autoplay: {
delay: 5000,
disableOnInteraction: false,
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
breakpoints: {
768: {
slidesPerView: 2,
spaceBetween: 30,
},
},
});
} else {
console.error('Swiper.js not loaded or failed to load.');
}
/* ================== [ Scroll-to-top code ] ================== */
const scrollTopBtn = document.getElementById('scrollTopBtn');
const progressRing = document.getElementById('progress-ring');
if (scrollTopBtn && progressRing) {
// Calculate circumference
const radius = progressRing.r.baseVal.value;
const circumference = 2 * Math.PI * radius;
// Set initial stroke-dasharray
progressRing.style.strokeDasharray = `${circumference} ${circumference}`;
const handleScrollProgress = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
// 1. Calculate scroll progress
// Ensure docHeight is not zero to avoid division by zero
const scrollPercent = docHeight > 0 ? (scrollTop / docHeight) : 0;
const dashoffset = circumference - (scrollPercent * circumference);
progressRing.style.strokeDashoffset = dashoffset;
// 2. Show/hide button
if (scrollTop > 300) { // Show after 300px of scroll
scrollTopBtn.classList.add('is-visible');
} else {
scrollTopBtn.classList.remove('is-visible');
}
};
window.addEventListener('scroll', handleScrollProgress);
}
/* ================== [ End of scroll-to-top code ] ================== */
// Chatbot Function
const chatToggleBtn = document.getElementById('chat-toggle-btn');
const chatbotWindow = document.getElementById('chatbot-window');
const chatbotCloseBtn = document.getElementById('chatbot-close-btn');
const chatbotForm = document.getElementById('chatbot-form');
const chatbotInput = document.getElementById('chatbot-input');
const chatbotMessages = document.getElementById('chatbot-messages');
if (chatToggleBtn && chatbotWindow && chatbotCloseBtn && chatbotForm && chatbotInput && chatbotMessages) {
// Toggle window visibility
const toggleChatbot = () => {
chatbotWindow.classList.toggle('is-visible');
chatToggleBtn.classList.toggle('is-visible'); // You can style this if needed
if (chatbotWindow.classList.contains('is-visible')) {
chatbotInput.focus();
}
};
chatToggleBtn.addEventListener('click', toggleChatbot);
chatbotCloseBtn.addEventListener('click', toggleChatbot);
// Handle sending a message
chatbotForm.addEventListener('submit', (e) => {
e.preventDefault();
const messageText = chatbotInput.value.trim();
if (messageText === "") return;
// 1. Add user's message
appendMessage(messageText, 'user');
chatbotInput.value = "";
// 2. Add dummy bot reply
setTimeout(() => {
appendMessage("Thank you for your message. An agent will be in touch with you shortly regarding your inquiry.", 'bot');
}, 1000);
});
// Function to add a message to the chat window
const appendMessage = (text, type) => {
const messageElement = document.createElement('div');
messageElement.classList.add('chat-message', type);
messageElement.textContent = text;
chatbotMessages.appendChild(messageElement);
// Scroll to bottom
chatbotMessages.scrollTop = chatbotMessages.scrollHeight;
};
}
});Final Output:
Conclusion:
You've just built a professional real estate website homepage using HTML, Tailwind CSS, and a few powerful JavaScript plugins. This free template gives you a massive head start on your project.
Want the Full Website?
This free homepage is just the beginning. A complete website needs an "About" page, "Listings" page, "Property Details" page, "Contact" page, and more.
I've built a full, premium 12-page version of this template. It includes everything you need for a complete, professional real estate website.
You can get the full version at my shop: Visit codewithfaraz.com/shop
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊


