Blog

Blog · Templates

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.

XCODX Team · 4 min read

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 and autocomplete.
  • 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 fetch at 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?
Use a hosted form service like Formspree, Web3Forms or Netlify Forms. You point the form (or a 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?
Yes. Every field has a real <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?
Add a hidden "honeypot" field that real users leave empty and bots tend to fill, and reject submissions where it’s filled. For more protection add a captcha (like a privacy-friendly one) and rate-limit submissions on your server or form service. Always validate and sanitize input on the backend.
Should I validate the form on the client or the server?
Both. Client-side validation (as in this template) gives instant feedback and a better experience, but it can be bypassed, so the server must re-validate every field before trusting or storing the data. Treat client validation as UX and server validation as security.

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.