A static site still needs a contact form, and the honest options are fewer and better than any stackOverflow thread implies. The pattern is the same for all of them: browser validation stays the validator, and the destination changes only.
const form = document.querySelector("#contact");
if (!form.reportValidity()) return; // browser already showed the errors
const data = Object.fromEntries(new FormData(form));
const body = JSON.stringify(data, null, 2);
// Destination A: the mail object, zero backend, opens the reader
location.href =
"mailto:hello@example.com" +
"?subject=" + encodeURIComponent(data.subject) +
"&body=" + encodeURIComponent(body);The destination ladder, easiest last
- GET method to a search or results page: the most forgiving static contact form, and it is this site's own search behavior.
- mailto with subject and body prebuilt: no server lines anywhere, no privacy tradeoff beyond the mail client itself.
- Your social or CRM's public form endpoints: the third-party takes the data, so read that privacy page twice before wiring it up.
- Host-provided form services: some static hosts ship a forms feature (Netlify's is one); it is a real backend installed under your URL, and its spam story comes with your host.
The validation layer
- required, type="email", minlength: browser constraints first, error rendering for free.
- reportValidity() for programmatic paths: one call surfaces the browser's native error bubble where the form left off.
- A custom visual layer on top when the design needs it: this site's form-validity article has the CSS recipe.
- Honeypot fields on backend cases and rate limits from the host; static does not mean unspammable, say that loudly.
The gacor summary: the browser validates, the platform transports, and your build step ships nothing. Contact form libraries are a symptom of forgetting that the form element never left.
