Free Contact Form Template (HTML, CSS & JS, 2026)
An accessible contact form template in HTML, CSS and JavaScript — name, email and message fields with inline validation and a success message — responsive and ready to connect to your backend.
This free contact form template is an accessible, responsive form in HTML, CSS and a little JavaScript: name, email and message fields with real labels, inline validation and a success message. It’s dependency-free and built to the accessibility fundamentals, so it works for everyone. Connect it to a hosted form service or your own backend and start receiving messages.
What you get
- Accessible fields with real
<label>s, required states andautocomplete. - Inline validation with errors announced via
aria-live, and a success message. - A responsive card layout that works on any screen.
- CSS-variable theming and zero dependencies.
The HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Contact us</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="wrap">
<form class="card" id="contact" novalidate>
<h1>Get in touch</h1>
<p class="sub">We usually reply within one business day.</p>
<div class="field">
<label for="name">Name</label>
<input id="name" name="name" type="text" autocomplete="name" required>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required
placeholder="[email protected]">
</div>
<div class="field">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required
minlength="10"></textarea>
</div>
<p class="error" id="error" role="alert" aria-live="polite"></p>
<button type="submit" class="btn">Send message</button>
<p class="ok" id="ok" role="status" aria-live="polite"></p>
</form>
</main>
<script src="contact.js"></script>
</body>
</html>
The CSS
:root {
--bg: #f4f6fb; --card: #ffffff; --text: #121826; --muted: #5b6472;
--accent: #4f46e5; --border: #e6e9f0; --error: #dc2626; --ok: #16a34a; --radius: 12px;
color-scheme: light;
}
* { box-sizing: border-box; margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: var(--bg); color: var(--text); }
.wrap { min-height: 100vh; display: grid; place-items: center; padding: 20px; }
.card { width: 100%; max-width: 480px; background: var(--card);
border: 1px solid var(--border); border-radius: 16px; padding: 32px; }
h1 { font-size: 24px; }
.sub { color: var(--muted); margin: 6px 0 22px; }
.field { margin-bottom: 16px; }
label { display: block; font-weight: 600; font-size: 14px; margin-bottom: 6px; }
input, textarea { width: 100%; padding: 12px 14px; border-radius: var(--radius);
border: 1px solid var(--border); font-size: 15px; font-family: inherit; color: var(--text); background: #fff; }
textarea { resize: vertical; }
input:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.error { color: var(--error); font-size: 14px; min-height: 20px; }
.ok { color: var(--ok); font-size: 14px; min-height: 20px; margin-top: 10px; }
.btn { width: 100%; padding: 13px; border: 0; border-radius: var(--radius);
background: var(--accent); color: #fff; font-weight: 700; font-size: 15px; cursor: pointer; margin-top: 6px; }
.btn:hover { filter: brightness(1.06); }
The JavaScript
Save as contact.js. It validates on the client for a good experience and shows a success message — the actual sending happens on your backend or form service.
const form = document.getElementById('contact');
const error = document.getElementById('error');
const ok = document.getElementById('ok');
form.addEventListener('submit', async (e) => {
e.preventDefault();
error.textContent = ''; ok.textContent = '';
const { name, email, message } = form.elements;
if (!name.value.trim()) return fail(name, 'Please enter your name.');
if (!email.validity.valid) return fail(email, 'Enter a valid email address.');
if (message.value.trim().length < 10) return fail(message, 'Message must be at least 10 characters.');
try {
// TODO: replace with your form endpoint (Formspree, your API, etc.)
// await fetch('/api/contact', { method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ name: name.value, email: email.value, message: message.value }) });
ok.textContent = "Thanks! Your message has been sent.";
form.reset();
} catch (err) {
error.textContent = 'Something went wrong. Please try again.';
}
});
function fail(field, msg) { error.textContent = msg; field.focus(); }
How to deliver the messages
- Use a form service (Formspree, Web3Forms, Netlify Forms) for a no-backend option — point the
fetchat their endpoint. - Or your own API: POST the JSON to your server over HTTPS and email or store it — see the complete guide to REST APIs.
- Stop spam: add a honeypot field or a captcha, and rate-limit submissions on the server.
- Validate server-side too: client validation is for UX; always re-check on the server.
- Confirm to the user: the success message reassures them; consider an auto-reply email.
Frequently asked questions
How do I make a contact form send email without a backend?
fetch request) at their endpoint, and they email you each submission — no server to build or maintain. It’s the fastest way to make a static site’s contact form actually deliver messages.Is this contact form accessible?
<label> tied to its input, required fields are marked, errors are announced to screen readers via role="alert"/aria-live, the success message uses role="status", and focus states are visible. On validation failure it moves focus to the offending field. These are the fundamentals that make a form usable for everyone.How do I stop spam on the contact form?
Should I validate the form on the client or the server?
Build and test this contact form live in XCODX Studio. See also the login page template, accessibility best practices and the complete guide to REST APIs.