How to create a contact form in HTML: complete guide
A contact form is one of the most common features on any website. If your site is built with a static site generator like Astro, Hugo, Next.js static export, or plain HTML, you might think you need a backend server to handle form submissions. You do not.
This guide walks through building a complete, production-ready HTML contact form from scratch, including accessible markup, client-side validation, spam protection, and email delivery through a hosted form endpoint.
The basic HTML contact form
Every HTML form needs three things: a <form> element, input fields with name attributes, and a submit button.
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<label for="name">Name</label> <input type="text" id="name" name="name" required />
<label for="email">Email</label> <input type="email" id="email" name="email" required />
<label for="message">Message</label> <textarea id="message" name="message" rows="5" required></textarea>
<button type="submit">Send message</button></form>The action attribute tells the browser where to send the form data. The method="POST" attribute sends the data as a POST request. Each field needs a name attribute so the backend knows what each value represents.
The hidden access_key field identifies your form to the backend service. Replace YOUR_ACCESS_KEY with the key from your form backend dashboard.
Adding accessible labels and structure
Accessible forms help all users, including those using screen readers. Every input should have a visible <label> associated via the for attribute matching the input id.
Group related fields with <fieldset> and <legend> when appropriate:
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<fieldset> <legend>Your information</legend>
<label for="name">Full name</label> <input type="text" id="name" name="name" autocomplete="name" required />
<label for="email">Email address</label> <input type="email" id="email" name="email" autocomplete="email" required /> </fieldset>
<label for="message">How can we help?</label> <textarea id="message" name="message" rows="5" maxlength="2000" required></textarea>
<button type="submit">Send message</button></form>The autocomplete attribute helps browsers fill in known values. The maxlength attribute prevents excessively long submissions.
Adding HTML5 validation
HTML5 provides built-in validation attributes that work without JavaScript:
| Attribute | Purpose |
|---|---|
required | Field must not be empty |
type="email" | Must be a valid email format |
type="url" | Must be a valid URL |
type="tel" | Optimizes mobile keyboard for phone numbers |
minlength | Minimum character count |
maxlength | Maximum character count |
pattern | Custom regex validation |
min / max | Numeric range limits |
Example with validation:
<label for="email">Work email</label><input type="email" id="email" name="email" required pattern="^[^@\s]+@[^@\s]+\.[^@\s]+$" title="Enter a valid email address"/>
<label for="phone">Phone (optional)</label><input type="tel" id="phone" name="phone" pattern="[0-9\-\+\(\)\s]{7,20}" title="Enter a valid phone number"/>The title attribute provides the validation message shown when the pattern does not match.
Adding spam protection with a honeypot field
Bots that crawl the web and fill out forms typically populate every visible field. A honeypot field is a hidden input that legitimate users never see or fill in. If the field has a value when the form is submitted, the submission is from a bot.
<input type="text" name="botcheck" style="position: absolute; left: -9999px; opacity: 0;" tabindex="-1" autocomplete="off"/>Place this inside your <form> element. Do not use display: none or type="hidden" because sophisticated bots check for those. The CSS positioning approach hides the field visually while keeping it in the DOM.
Most form backend services, including FormsFort, automatically reject submissions where the honeypot field has a value.
Adding a CAPTCHA for stronger spam protection
For high-traffic forms, add a CAPTCHA challenge alongside the honeypot. FormsFort supports Turnstile, hCaptcha, and reCAPTCHA:
<div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div><script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>The CAPTCHA response token is automatically included in the form submission. Your form backend verifies it server-side before processing.
Handling file uploads
To accept file attachments, set the form enctype to multipart/form-data and add a file input:
<form action="https://api.formsfort.com/submit" method="POST" enctype="multipart/form-data"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<label for="resume">Upload your resume</label> <input type="file" id="resume" name="resume" accept=".pdf,.doc,.docx" />
<button type="submit">Submit application</button></form>The accept attribute restricts the file picker to specific file types. Your form backend should also validate MIME types and scan files for malware.
Submitting via AJAX with JavaScript
For a smoother user experience, submit the form without a full page reload:
<form id="contactForm" action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label for="email">Email</label> <input type="email" id="email" name="email" required /> <label for="message">Message</label> <textarea id="message" name="message" required></textarea> <button type="submit">Send</button> <p id="formStatus" aria-live="polite"></p></form>
<script> document.getElementById("contactForm").addEventListener("submit", async (e) => { e.preventDefault(); const form = e.target; const status = document.getElementById("formStatus"); status.textContent = "Sending...";
try { const response = await fetch(form.action, { method: "POST", body: new FormData(form), }); if (response.ok) { status.textContent = "Message sent. We will be in touch."; form.reset(); } else { status.textContent = "Something went wrong. Please try again."; } } catch { status.textContent = "Network error. Please check your connection."; } });</script>The aria-live="polite" attribute ensures screen readers announce the status change.
Redirecting after submission
To redirect users to a thank-you page after a successful standard (non-AJAX) submission, add a hidden _redirect field:
<input type="hidden" name="_redirect" value="https://example.com/thanks" />Complete production-ready contact form
Here is a complete contact form combining all the techniques above:
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <input type="hidden" name="_redirect" value="https://example.com/thanks" /> <input type="hidden" name="_subject" value="New contact form submission" />
<input type="text" name="botcheck" style="position: absolute; left: -9999px; opacity: 0;" tabindex="-1" autocomplete="off" />
<label for="name">Name</label> <input type="text" id="name" name="name" autocomplete="name" required />
<label for="email">Email</label> <input type="email" id="email" name="email" autocomplete="email" required />
<label for="message">Message</label> <textarea id="message" name="message" rows="5" maxlength="2000" required></textarea>
<button type="submit">Send message</button></form>Choosing a form backend
If you are hosting a static site, you need a service to receive and process submissions. Popular options include:
| Service | Free tier | File uploads | Webhooks | Google Sheets |
|---|---|---|---|---|
| FormsFort | 100/mo | Yes | Yes | Yes |
| Formspree | 50/mo | Yes | Yes | No |
| Getform | 50/mo | Yes | Yes | No |
| Basin | 100/mo | Yes | Yes | No |
Choose based on your submission volume, integration needs, and spam protection requirements.
Summary
Building an HTML contact form requires no backend code. Structure your markup with accessible labels, add HTML5 validation attributes, protect against spam with a honeypot field, and point the form action at a hosted endpoint. The result is a production-ready form that works on any static website.
Frequently asked questions
Can I create a contact form with just HTML?
Yes. You can build the entire form with HTML markup. To receive submissions without a backend server, point the form action at a hosted form endpoint like FormsFort, Formspree, or Getform.
How do I make my HTML form send emails?
Add an action attribute pointing to a form backend service. The service receives the submission, processes spam checks, and delivers the data via email, webhook, or spreadsheet.
What is the best way to prevent spam on an HTML contact form?
Use a combination of a hidden honeypot field, a CAPTCHA challenge, rate limiting by IP, and allowed domain restrictions. A honeypot field catches most bots without affecting user experience.
Do I need JavaScript for an HTML contact form?
No. A standard HTML form with method='POST' and an action URL works without JavaScript. You can add JavaScript for AJAX submission and custom validation, but it is not required.
How do I add file uploads to an HTML contact form?
Set the form enctype to multipart/form-data and add an input with type='file'. The form backend must support file uploads and scanning.
Get started free
Ready to add forms to your static site?
No backend required. Point your HTML form at FormsFort and start receiving submissions in minutes.