Guide

How to create a contact form in HTML (with a working backend)

A working HTML contact form has three pieces: semantic markup, basic validation, and somewhere to send the submission. This guide walks through all three, with a copy-pasteable example you can drop into any static site, Webflow project, or custom HTML page.

1. The minimum markup

Start with a plain <form> element. Use a <label> for every field so screen readers and password managers can identify inputs correctly, and pick the right type for each one — type="email" gets you a free format check in every modern browser.

<form action="https://b2bform.app/api/public/submit/YOUR_FORM_ID" method="POST">
  <label for="name">Name</label>
  <input id="name" name="name" type="text" required />

  <label for="email">Email</label>
  <input id="email" name="email" type="email" required />

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="4" required></textarea>

  <button type="submit">Send</button>
</form>

2. Add validation that works without JavaScript

HTML5 attributes cover most validation needs without any JavaScript. They run before the form is submitted, show native error messages, and degrade gracefully on older browsers.

  • required — block submission when the field is empty.
  • type="email" — require a valid email format.
  • minlength / maxlength — enforce text length on inputs and textareas.
  • pattern — match a custom regex (phone numbers, postal codes, etc.).
  • autocomplete — let browsers and password managers fill fields correctly.

3. Send submissions somewhere

A form without a backend just refreshes the page. You have two options: write a server endpoint yourself (Node, PHP, a serverless function) or point the action at a form backend that handles delivery, spam filtering, and notifications for you.

With Formpost you create a form in the dashboard, copy its endpoint, and paste it into the action attribute. Submissions land in your dashboard immediately and Pro accounts get email notifications on every new lead.

<form action="https://b2bform.app/api/public/submit/YOUR_FORM_ID"
      method="POST">
  <!-- fields … -->
</form>

4. Handle the response (optional)

By default the browser navigates to the endpoint's response page after submitting. To keep the user on your page — and show a custom "thanks" message — submit with fetch() instead:

const form = document.querySelector("form");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const data = new FormData(form);

  const res = await fetch(form.action, {
    method: "POST",
    body: data,
    headers: { Accept: "application/json" },
  });

  if (res.ok) {
    form.reset();
    document.querySelector("#thanks").hidden = false;
  } else {
    document.querySelector("#error").hidden = false;
  }
});

Accessibility checklist

  • Every input has a matching <label> with a for attribute.
  • Required fields are marked with the required attribute, not just an asterisk.
  • Error messages are programmatically associated with their input via aria-describedby.
  • The submit button is a real <button type="submit">, not a styled <div>.
  • Focus states are visible — never remove the outline without replacing it.

Common mistakes to avoid

  • Using a mailto: link. It opens the user's email client (if they have one configured), often fails silently, and loses every submission from users on shared or mobile devices.
  • No spam protection. Even small forms get bot traffic within hours of going live. A form backend with built-in spam filtering saves you from cleaning up the inbox manually.
  • Missing name attributes. Fields without a name are silently dropped from the submission — always set both id and name.

Skip the backend — try Formpost free

Create a form ID, paste the endpoint into your HTML, and start collecting submissions. Free plan includes 50 submissions per month, no credit card required.

Get your endpoint