Pages Easy
Contact Page
A contact page with a validated multi-field form, map placeholder, office address cards, and social links. No libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f9fafb;
color: #111;
}
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 40px;
background: #fff;
border-bottom: 1px solid #e5e7eb;
position: sticky;
top: 0;
z-index: 10;
}
.logo {
font-size: 20px;
font-weight: 800;
text-decoration: none;
color: #111;
}
nav {
display: flex;
gap: 24px;
}
nav a {
font-size: 14px;
color: #555;
text-decoration: none;
}
.page-content {
max-width: 1080px;
margin: 0 auto;
padding: 60px 24px 80px;
}
.page-header {
text-align: center;
margin-bottom: 48px;
}
.page-header h1 {
font-size: clamp(28px, 5vw, 44px);
font-weight: 900;
margin-bottom: 14px;
}
.page-header p {
font-size: 17px;
color: #555;
}
/* Layout */
.contact-layout {
display: grid;
grid-template-columns: 1fr 380px;
gap: 40px;
align-items: start;
}
@media (max-width: 860px) {
.contact-layout {
grid-template-columns: 1fr;
}
}
/* Form card */
.form-card {
background: #fff;
border-radius: 20px;
border: 1px solid #e5e7eb;
padding: 32px;
position: relative;
min-height: 400px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
@media (max-width: 560px) {
.form-row {
grid-template-columns: 1fr;
}
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 16px;
}
.field label {
font-size: 13px;
font-weight: 600;
color: #555;
display: flex;
justify-content: space-between;
align-items: center;
}
.field input,
.field textarea,
.field select {
padding: 11px 14px;
border: 1.5px solid #e5e7eb;
border-radius: 10px;
font-size: 14px;
outline: none;
font-family: inherit;
color: #111;
transition: border-color 0.15s;
background: #fff;
}
.field input:focus,
.field textarea:focus,
.field select:focus {
border-color: #6366f1;
}
.field input.error,
.field textarea.error {
border-color: #ef4444;
}
.field textarea {
resize: vertical;
line-height: 1.6;
}
.char-count {
font-size: 12px;
color: #aaa;
font-weight: 400;
}
.field-error {
font-size: 12px;
color: #ef4444;
min-height: 16px;
}
.submit-btn {
width: 100%;
padding: 14px;
background: #111;
color: #fff;
border: none;
border-radius: 12px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: opacity 0.15s;
}
.submit-btn:active {
opacity: 0.85;
}
.submit-btn.loading {
opacity: 0.7;
cursor: wait;
}
/* Success state */
.form-success {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 16px;
position: absolute;
inset: 0;
border-radius: 20px;
background: #fff;
padding: 40px;
}
.form-success.visible {
display: flex;
}
.form-success svg {
width: 56px;
height: 56px;
}
.form-success h3 {
font-size: 22px;
font-weight: 800;
}
.form-success p {
font-size: 15px;
color: #555;
}
.back-btn {
padding: 10px 24px;
background: #f3f4f6;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
color: #555;
transition: background 0.15s;
}
.back-btn:hover {
background: #e5e7eb;
}
/* Info panel */
.info-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.map-placeholder {
background: #e5e7eb;
border-radius: 16px;
overflow: hidden;
}
.map-placeholder svg {
width: 100%;
height: auto;
display: block;
}
.office-cards {
display: flex;
flex-direction: column;
gap: 12px;
}
.office-card {
display: flex;
align-items: flex-start;
gap: 14px;
padding: 16px;
background: #fff;
border-radius: 12px;
border: 1px solid #e5e7eb;
}
.office-icon {
width: 40px;
height: 40px;
background: #eef2ff;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.office-icon svg {
width: 18px;
height: 18px;
color: #6366f1;
}
.office-card strong {
font-size: 14px;
color: #111;
display: block;
margin-bottom: 4px;
}
.office-card p {
font-size: 13px;
color: #666;
line-height: 1.5;
}
.social-row {
display: flex;
gap: 16px;
}
.social-row a {
font-size: 14px;
color: #6366f1;
text-decoration: none;
font-weight: 500;
}
.social-row a:hover {
text-decoration: underline;
}const form = document.getElementById("contactForm");
const success = document.getElementById("formSuccess");
const backBtn = document.getElementById("backBtn");
const submitBtn = document.getElementById("submitBtn");
const submitText = document.getElementById("submitText");
const message = document.getElementById("message");
const charCount = document.getElementById("charCount");
// Character counter
message.addEventListener("input", () => {
charCount.textContent = `${message.value.length} / 500`;
});
// Validation
function validate() {
let valid = true;
["name", "email", "message"].forEach((id) => {
const input = document.getElementById(id);
const error = input.parentElement.querySelector(".field-error");
if (!input.value.trim()) {
input.classList.add("error");
if (error) error.textContent = "This field is required";
valid = false;
} else if (id === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value)) {
input.classList.add("error");
if (error) error.textContent = "Enter a valid email address";
valid = false;
} else {
input.classList.remove("error");
if (error) error.textContent = "";
}
});
return valid;
}
form.querySelectorAll("input, textarea").forEach((el) => {
el.addEventListener("input", () => {
el.classList.remove("error");
const error = el.parentElement.querySelector(".field-error");
if (error) error.textContent = "";
});
});
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!validate()) return;
submitBtn.classList.add("loading");
submitText.textContent = "Sending…";
setTimeout(() => {
submitBtn.classList.remove("loading");
submitText.textContent = "Send Message";
success.classList.add("visible");
}, 1000);
});
backBtn.addEventListener("click", () => {
success.classList.remove("visible");
form.reset();
charCount.textContent = "0 / 500";
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Contact Us</title>
</head>
<body>
<header class="site-header">
<a href="#" class="logo">Brand</a>
<nav><a href="#">Products</a><a href="#">Docs</a><a href="#">Blog</a></nav>
</header>
<main class="page-content">
<div class="page-header">
<h1>Get in touch</h1>
<p>Have a question, a project idea, or just want to say hi? We'd love to hear from you.</p>
</div>
<div class="contact-layout">
<!-- Form -->
<div class="form-card">
<form id="contactForm" novalidate>
<div class="form-row">
<div class="field">
<label for="name">Full Name</label>
<input type="text" id="name" name="name" placeholder="Alex Morgan" required />
<span class="field-error"></span>
</div>
<div class="field">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" placeholder="alex@example.com" required />
<span class="field-error"></span>
</div>
</div>
<div class="field">
<label for="subject">Subject</label>
<select id="subject" name="subject">
<option value="">Select a topic…</option>
<option>General Inquiry</option>
<option>Partnership</option>
<option>Bug Report</option>
<option>Feature Request</option>
<option>Other</option>
</select>
</div>
<div class="field">
<label for="message">Message <span class="char-count" id="charCount">0 / 500</span></label>
<textarea id="message" name="message" placeholder="Tell us more…" rows="5" maxlength="500" required></textarea>
<span class="field-error"></span>
</div>
<button type="submit" class="submit-btn" id="submitBtn">
<span id="submitText">Send Message</span>
</button>
</form>
<!-- Success state -->
<div class="form-success" id="formSuccess">
<svg viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="20" fill="#f0fdf4" stroke="#86efac" stroke-width="2"/>
<polyline points="14,26 22,34 34,18" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<h3>Message Sent!</h3>
<p>Thanks for reaching out. We'll get back to you within 24 hours.</p>
<button class="back-btn" id="backBtn">Send Another</button>
</div>
</div>
<!-- Info panel -->
<div class="info-panel">
<!-- Map placeholder -->
<div class="map-placeholder">
<svg viewBox="0 0 200 120" fill="none">
<rect width="200" height="120" fill="#f3f4f6"/>
<rect x="10" y="10" width="180" height="100" rx="4" fill="#e5e7eb"/>
<!-- Simplified map grid -->
<line x1="0" y1="40" x2="200" y2="40" stroke="#d1d5db" stroke-width="0.5"/>
<line x1="0" y1="80" x2="200" y2="80" stroke="#d1d5db" stroke-width="0.5"/>
<line x1="50" y1="0" x2="50" y2="120" stroke="#d1d5db" stroke-width="0.5"/>
<line x1="100" y1="0" x2="100" y2="120" stroke="#d1d5db" stroke-width="0.5"/>
<line x1="150" y1="0" x2="150" y2="120" stroke="#d1d5db" stroke-width="0.5"/>
<!-- Pin -->
<circle cx="100" cy="54" r="12" fill="#6366f1" opacity="0.15"/>
<circle cx="100" cy="54" r="5" fill="#6366f1"/>
<line x1="100" y1="59" x2="100" y2="68" stroke="#6366f1" stroke-width="2" stroke-linecap="round"/>
<text x="50" y="102" font-size="9" fill="#9ca3af" font-family="sans-serif">Map placeholder — embed Google Maps or Leaflet</text>
</svg>
</div>
<div class="office-cards">
<div class="office-card">
<div class="office-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
</div>
<div>
<strong>San Francisco</strong>
<p>123 Main St, Suite 400<br>San Francisco, CA 94105</p>
</div>
</div>
<div class="office-card">
<div class="office-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9a16 16 0 0 0 6 6l.75-.75a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 21.48 17z"/></svg>
</div>
<div>
<strong>Phone</strong>
<p>+1 (415) 555-0100</p>
</div>
</div>
<div class="office-card">
<div class="office-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
</div>
<div>
<strong>Email</strong>
<p>hello@brand.com</p>
</div>
</div>
</div>
<div class="social-row">
<a href="#">Twitter</a>
<a href="#">GitHub</a>
<a href="#">LinkedIn</a>
<a href="#">Discord</a>
</div>
</div>
</div>
</main>
<script src="script.js"></script>
</body>
</html>Contact Page
A complete contact page with a two-column layout: contact form on the left and office info on the right. Includes client-side validation, character counter for the message field, and a map embed placeholder.
Features
- Contact form — name, email, subject, message with real-time validation
- Character counter — live count on the message textarea
- Success state — animated confirmation on submit
- Office cards — address, phone, email with icons
- Map placeholder — styled div ready for Google Maps or Leaflet embed
- Social links — icon row
When to use it
- Agency and freelancer contact pages
- SaaS support / sales contact form
- Any site that needs a “get in touch” page