# Show HN: I pasted 16 form generators' output into a blank page – none send

> Source: <https://formgong.com/en/tools/html-form-generator/>
> Published: 2026-11-10 19:59:38+00:00

## HTML form generator online

Pick a starting form or edit the sample. The preview and the HTML tab change as you type. Paste your access key and the preview's button sends a real test, so you can see the form work before it goes on your site.

**New:** [open this form in the form builder](https://formgong.com/en/tools/form-builder/?from=html-form-generator) to give it your site's colours and font and get the code for WordPress, Shopify, Wix or Webflow.

### 1. Start from a form

## Or describe the form and let AI fill in the fields

Only your description goes to the AI model. The answer replaces the fields above it, and you can edit them like any other.

### 2. Edit the fields

1. 
Files need Uploads turned on for your form, plus the CAPTCHA widget from its Connect page. PDF, JPEG or PNG, up to 5 MB each.
2. 
Files need Uploads turned on for your form, plus the CAPTCHA widget from its Connect page. PDF, JPEG or PNG, up to 5 MB each.
3. 
Files need Uploads turned on for your form, plus the CAPTCHA widget from its Connect page. PDF, JPEG or PNG, up to 5 MB each.
4. 
Files need Uploads turned on for your form, plus the CAPTCHA widget from its Connect page. PDF, JPEG or PNG, up to 5 MB each.
5. 
Files need Uploads turned on for your form, plus the CAPTCHA widget from its Connect page. PDF, JPEG or PNG, up to 5 MB each.

Files need Uploads turned on for your form, plus the CAPTCHA widget from its Connect page. PDF, JPEG or PNG, up to 5 MB each.

### 3. Check the live preview

This is the HTML tab, rendered. Paste your access key in step 4 and the button sends a real test submission.

### 4. Add your access key

Every copy below uses it as you type. Until then the code has fk_your_access_key, which Formgong refuses.

### 5. Copy the code

Plain HTML with a short style block. The script at the end is optional: it keeps visitors on the page and says sent only when Formgong answers success: true. Delete it and the browser posts the form on its own.

```
<style>
#fg-form{position:relative;box-sizing:border-box;display:grid;gap:16px;width:100%;max-width:520px;padding:28px;border:1px solid #dbe2ea;border-radius:8px;background:#fff;color:#0f172a;font:16px/1.5 system-ui,sans-serif}
#fg-form *{box-sizing:border-box}
#fg-form h2{margin:0;font-size:24px;line-height:1.3}
#fg-form .fg-field{display:grid;gap:6px;min-width:0;margin:0;padding:0;border:0}
#fg-form label,#fg-form legend{font-weight:500}
#fg-form legend{padding:0;margin-bottom:2px}
#fg-form input,#fg-form select,#fg-form textarea{width:100%;min-height:44px;padding:10px 12px;border:1px solid #64748b;border-radius:8px;background:#fff;color:inherit;font:inherit}
#fg-form textarea{min-height:120px;resize:vertical}
#fg-form .fg-choice{display:flex;align-items:center;gap:10px;font-weight:400}
#fg-form .fg-choice input{width:20px;min-height:20px;margin:0;flex-shrink:0}
#fg-form [aria-invalid=true]{border-color:#b91c1c}
#fg-form .fg-error{margin:0;color:#b91c1c;font-size:14px}
#fg-form .fg-status{margin:0}
#fg-form .fg-credit{margin:0;font-size:13px;text-align:center}
#fg-form .fg-credit a{color:#64748b}
#fg-form .fg-error:empty{display:none}
#fg-form button{min-height:48px;padding:12px 20px;border:0;border-radius:8px;background:#4338ca;color:#fff;font:inherit;font-weight:600;cursor:pointer}
#fg-form button:disabled{opacity:.7;cursor:wait}
#fg-form :focus-visible{outline:3px solid #4338ca;outline-offset:3px}
#fg-form .fg-trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
#fg-form select{appearance:none;-webkit-appearance:none;padding-inline-end:40px;background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) 50%,calc(100% - 15px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}#fg-form select:dir(rtl){background-position:15px 50%,20px 50%}@media (forced-colors:active){#fg-form select{appearance:auto;-webkit-appearance:auto;background-image:none}}
</style>
<form id="fg-form" action="https://formgong.com/submit" method="POST">
  <h2>Get in touch</h2>
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_lang" value="en">
  <div class="fg-field">
    <label for="fg-name">Name <span aria-hidden="true">*</span></label>
    <input type="text" id="fg-name" name="name" autocomplete="name" required aria-describedby="fg-name-error">
    <p class="fg-error" id="fg-name-error"></p>
  </div>
  <div class="fg-field">
    <label for="fg-email">Email <span aria-hidden="true">*</span></label>
    <input type="email" id="fg-email" name="email" autocomplete="email" required aria-describedby="fg-email-error">
    <p class="fg-error" id="fg-email-error"></p>
  </div>
  <div class="fg-field">
    <label for="fg-phone">Phone</label>
    <input type="tel" id="fg-phone" name="phone" autocomplete="tel" aria-describedby="fg-phone-error">
    <p class="fg-error" id="fg-phone-error"></p>
  </div>
  <div class="fg-field">
    <label for="fg-subject">Subject <span aria-hidden="true">*</span></label>
    <select id="fg-subject" name="subject" required aria-describedby="fg-subject-error"><option value="">Choose…</option><option value="A question">A question</option><option value="A quote">A quote</option><option value="Support">Support</option></select>
    <p class="fg-error" id="fg-subject-error"></p>
  </div>
  <div class="fg-field">
    <label for="fg-message">Message <span aria-hidden="true">*</span></label>
    <textarea id="fg-message" name="message" required aria-describedby="fg-message-error" rows="4"></textarea>
    <p class="fg-error" id="fg-message-error"></p>
  </div>
  <div class="fg-trap" aria-hidden="true"><label>Leave this empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label></div>
  <button type="submit">Send</button>
  <p class="fg-status" data-fg-status role="status"></p>
  <p class="fg-credit"><a href="https://formgong.com/en/?ref=badge&amp;utm_source=badge&amp;utm_medium=form&amp;utm_campaign=generator" target="_blank" rel="noopener">Form by Formgong</a></p>
</form>
<script>
// Optional. Without it the browser posts the form and Formgong shows its own thank-you or error page.
(function () {
  var form = document.getElementById("fg-form");
  var status = form.querySelector("[data-fg-status]");
  form.noValidate = true;
  form.addEventListener("submit", function (event) {
    event.preventDefault();
    var first = null, seen = {};
    Array.prototype.forEach.call(form.elements, function (field) {
      var id = field.getAttribute("aria-describedby");
      if (!id || seen[id]) return;
      seen[id] = true;
      var ok = field.checkValidity();
      field.setAttribute("aria-invalid", ok ? "false" : "true");
      document.getElementById(id).textContent = ok ? "" : field.validationMessage;
      if (!ok && !first) first = field;
    });
    if (first) { first.focus(); return; }
    var button = form.querySelector("[type=submit]");
    button.disabled = true;
    status.textContent = "Sending…";
    fetch(form.action, { method: "POST", headers: { Accept: "application/json" }, body: new FormData(form) })
      .then(function (response) { return response.json().catch(function () { return {}; }); })
      .then(function (data) {
        if (data.success === true) { form.reset(); status.textContent = "Thank you. Your message was sent."; }
        else status.textContent = data.message || "Could not send. Please try again.";
      })
      .catch(function () { status.textContent = "Could not reach the server. Check your connection and try again."; })
      .then(function () { button.disabled = false; });
  });
})();
</script>
<!-- Optional: fg.js counts views, starts and the field people stop at for your Formgong Analytics page. No cookies. Delete this line to turn it off. -->
<script src="https://formgong.com/fg.js" defer></script>
```

The same form with Tailwind classes. Your site has to load Tailwind; this page does not.

```
<form class="relative mx-auto grid w-full max-w-lg gap-4 rounded-lg border border-slate-200 bg-white p-6 text-base text-slate-900" id="tw-form" action="https://formgong.com/submit" method="POST">
  <h2 class="text-2xl font-semibold">Get in touch</h2>
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_lang" value="en">
  <div class="grid gap-1.5">
    <label class="font-medium" for="tw-name">Name <span aria-hidden="true">*</span></label>
    <input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 aria-[invalid=true]:border-red-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" type="text" id="tw-name" name="name" autocomplete="name" required aria-describedby="tw-name-error">
    <p class="text-sm text-red-700 empty:hidden" id="tw-name-error"></p>
  </div>
  <div class="grid gap-1.5">
    <label class="font-medium" for="tw-email">Email <span aria-hidden="true">*</span></label>
    <input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 aria-[invalid=true]:border-red-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" type="email" id="tw-email" name="email" autocomplete="email" required aria-describedby="tw-email-error">
    <p class="text-sm text-red-700 empty:hidden" id="tw-email-error"></p>
  </div>
  <div class="grid gap-1.5">
    <label class="font-medium" for="tw-phone">Phone</label>
    <input class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 aria-[invalid=true]:border-red-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" type="tel" id="tw-phone" name="phone" autocomplete="tel" aria-describedby="tw-phone-error">
    <p class="text-sm text-red-700 empty:hidden" id="tw-phone-error"></p>
  </div>
  <div class="grid gap-1.5">
    <label class="font-medium" for="tw-subject">Subject <span aria-hidden="true">*</span></label>
    <select class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 aria-[invalid=true]:border-red-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700" id="tw-subject" name="subject" required aria-describedby="tw-subject-error"><option value="">Choose…</option><option value="A question">A question</option><option value="A quote">A quote</option><option value="Support">Support</option></select>
    <p class="text-sm text-red-700 empty:hidden" id="tw-subject-error"></p>
  </div>
  <div class="grid gap-1.5">
    <label class="font-medium" for="tw-message">Message <span aria-hidden="true">*</span></label>
    <textarea class="w-full rounded-md border border-slate-400 bg-white px-3 py-2 text-base text-slate-900 aria-[invalid=true]:border-red-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-700 min-h-28" id="tw-message" name="message" required aria-describedby="tw-message-error" rows="4"></textarea>
    <p class="text-sm text-red-700 empty:hidden" id="tw-message-error"></p>
  </div>
  <div class="absolute -left-[9999px] h-px w-px overflow-hidden" aria-hidden="true"><label>Leave this empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label></div>
  <button class="min-h-12 rounded-md bg-indigo-700 px-4 py-3 font-semibold text-white disabled:opacity-70" type="submit">Send</button>
  <p class="empty:hidden" data-fg-status role="status"></p>
  <p class="text-center text-sm text-slate-500"><a href="https://formgong.com/en/?ref=badge&amp;utm_source=badge&amp;utm_medium=form&amp;utm_campaign=generator" target="_blank" rel="noopener">Form by Formgong</a></p>
</form>
<script>
// Optional. Without it the browser posts the form and Formgong shows its own thank-you or error page.
(function () {
  var form = document.getElementById("tw-form");
  var status = form.querySelector("[data-fg-status]");
  form.noValidate = true;
  form.addEventListener("submit", function (event) {
    event.preventDefault();
    var first = null, seen = {};
    Array.prototype.forEach.call(form.elements, function (field) {
      var id = field.getAttribute("aria-describedby");
      if (!id || seen[id]) return;
      seen[id] = true;
      var ok = field.checkValidity();
      field.setAttribute("aria-invalid", ok ? "false" : "true");
      document.getElementById(id).textContent = ok ? "" : field.validationMessage;
      if (!ok && !first) first = field;
    });
    if (first) { first.focus(); return; }
    var button = form.querySelector("[type=submit]");
    button.disabled = true;
    status.textContent = "Sending…";
    fetch(form.action, { method: "POST", headers: { Accept: "application/json" }, body: new FormData(form) })
      .then(function (response) { return response.json().catch(function () { return {}; }); })
      .then(function (data) {
        if (data.success === true) { form.reset(); status.textContent = "Thank you. Your message was sent."; }
        else status.textContent = data.message || "Could not send. Please try again.";
      })
      .catch(function () { status.textContent = "Could not reach the server. Check your connection and try again."; })
      .then(function () { button.disabled = false; });
  });
})();
</script>
<!-- Optional: fg.js counts views, starts and the field people stop at for your Formgong Analytics page. No cookies. Delete this line to turn it off. -->
<script src="https://formgong.com/fg.js" defer></script>
```

The same form with Bootstrap 5 classes. Your site has to load Bootstrap; this page does not.

```
<form class="position-relative mx-auto" id="bs-form" action="https://formgong.com/submit" method="POST">
  <h2 class="h4 mb-3">Get in touch</h2>
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_lang" value="en">
  <div class="mb-3">
    <label class="form-label" for="bs-name">Name <span aria-hidden="true">*</span></label>
    <input class="form-control" type="text" id="bs-name" name="name" autocomplete="name" required aria-describedby="bs-name-error">
    <p class="form-text text-danger" id="bs-name-error"></p>
  </div>
  <div class="mb-3">
    <label class="form-label" for="bs-email">Email <span aria-hidden="true">*</span></label>
    <input class="form-control" type="email" id="bs-email" name="email" autocomplete="email" required aria-describedby="bs-email-error">
    <p class="form-text text-danger" id="bs-email-error"></p>
  </div>
  <div class="mb-3">
    <label class="form-label" for="bs-phone">Phone</label>
    <input class="form-control" type="tel" id="bs-phone" name="phone" autocomplete="tel" aria-describedby="bs-phone-error">
    <p class="form-text text-danger" id="bs-phone-error"></p>
  </div>
  <div class="mb-3">
    <label class="form-label" for="bs-subject">Subject <span aria-hidden="true">*</span></label>
    <select class="form-select" id="bs-subject" name="subject" required aria-describedby="bs-subject-error"><option value="">Choose…</option><option value="A question">A question</option><option value="A quote">A quote</option><option value="Support">Support</option></select>
    <p class="form-text text-danger" id="bs-subject-error"></p>
  </div>
  <div class="mb-3">
    <label class="form-label" for="bs-message">Message <span aria-hidden="true">*</span></label>
    <textarea class="form-control" id="bs-message" name="message" required aria-describedby="bs-message-error" rows="4"></textarea>
    <p class="form-text text-danger" id="bs-message-error"></p>
  </div>
  <div class="visually-hidden" aria-hidden="true"><label>Leave this empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label></div>
  <button class="btn btn-primary" type="submit">Send</button>
  <p class="mt-3 mb-0" data-fg-status role="status"></p>
  <p class="text-center small text-secondary mt-2 mb-0"><a href="https://formgong.com/en/?ref=badge&amp;utm_source=badge&amp;utm_medium=form&amp;utm_campaign=generator" target="_blank" rel="noopener">Form by Formgong</a></p>
</form>
<script>
// Optional. Without it the browser posts the form and Formgong shows its own thank-you or error page.
(function () {
  var form = document.getElementById("bs-form");
  var status = form.querySelector("[data-fg-status]");
  form.noValidate = true;
  form.addEventListener("submit", function (event) {
    event.preventDefault();
    var first = null, seen = {};
    Array.prototype.forEach.call(form.elements, function (field) {
      var id = field.getAttribute("aria-describedby");
      if (!id || seen[id]) return;
      seen[id] = true;
      var ok = field.checkValidity();
      field.setAttribute("aria-invalid", ok ? "false" : "true");
      document.getElementById(id).textContent = ok ? "" : field.validationMessage;
      if (!ok && !first) first = field;
    });
    if (first) { first.focus(); return; }
    var button = form.querySelector("[type=submit]");
    button.disabled = true;
    status.textContent = "Sending…";
    fetch(form.action, { method: "POST", headers: { Accept: "application/json" }, body: new FormData(form) })
      .then(function (response) { return response.json().catch(function () { return {}; }); })
      .then(function (data) {
        if (data.success === true) { form.reset(); status.textContent = "Thank you. Your message was sent."; }
        else status.textContent = data.message || "Could not send. Please try again.";
      })
      .catch(function () { status.textContent = "Could not reach the server. Check your connection and try again."; })
      .then(function () { button.disabled = false; });
  });
})();
</script>
<!-- Optional: fg.js counts views, starts and the field people stop at for your Formgong Analytics page. No cookies. Delete this line to turn it off. -->
<script src="https://formgong.com/fg.js" defer></script>
```

A component with its own styles. It checks every field, sends FormData, and says sent only when Formgong answers success: true.

``` js
import { useEffect, useState } from "react";

const ENDPOINT = "https://formgong.com/submit";
const CSS = "#fg-form{position:relative;box-sizing:border-box;display:grid;gap:16px;width:100%;max-width:520px;padding:28px;border:1px solid #dbe2ea;border-radius:8px;background:#fff;color:#0f172a;font:16px/1.5 system-ui,sans-serif}\n#fg-form *{box-sizing:border-box}\n#fg-form h2{margin:0;font-size:24px;line-height:1.3}\n#fg-form .fg-field{display:grid;gap:6px;min-width:0;margin:0;padding:0;border:0}\n#fg-form label,#fg-form legend{font-weight:500}\n#fg-form legend{padding:0;margin-bottom:2px}\n#fg-form input,#fg-form select,#fg-form textarea{width:100%;min-height:44px;padding:10px 12px;border:1px solid #64748b;border-radius:8px;background:#fff;color:inherit;font:inherit}\n#fg-form textarea{min-height:120px;resize:vertical}\n#fg-form .fg-choice{display:flex;align-items:center;gap:10px;font-weight:400}\n#fg-form .fg-choice input{width:20px;min-height:20px;margin:0;flex-shrink:0}\n#fg-form [aria-invalid=true]{border-color:#b91c1c}\n#fg-form .fg-error{margin:0;color:#b91c1c;font-size:14px}\n#fg-form .fg-status{margin:0}\n#fg-form .fg-credit{margin:0;font-size:13px;text-align:center}\n#fg-form .fg-credit a{color:#64748b}\n#fg-form .fg-error:empty{display:none}\n#fg-form button{min-height:48px;padding:12px 20px;border:0;border-radius:8px;background:#4338ca;color:#fff;font:inherit;font-weight:600;cursor:pointer}\n#fg-form button:disabled{opacity:.7;cursor:wait}\n#fg-form :focus-visible{outline:3px solid #4338ca;outline-offset:3px}\n#fg-form .fg-trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}\n#fg-form select{appearance:none;-webkit-appearance:none;padding-inline-end:40px;background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) 50%,calc(100% - 15px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}#fg-form select:dir(rtl){background-position:15px 50%,20px 50%}@media (forced-colors:active){#fg-form select{appearance:auto;-webkit-appearance:auto;background-image:none}}";

export default function GeneratedForm() {
  const [errors, setErrors] = useState({});
  const [status, setStatus] = useState("");
  const [busy, setBusy] = useState(false);
  // Formgong analytics: form views, starts and the field people stop at. No cookies.
  // Optional: fg.js counts views, starts and drop-off for your Formgong Analytics page. No cookies.
  useEffect(() => { if (!document.querySelector('script[src$="/fg.js"]')) document.head.appendChild(Object.assign(document.createElement("script"), { src: "https://formgong.com/fg.js", defer: true })); }, []);

  async function onSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    const found = {};
    for (const field of form.elements) {
      if (field.name && !(field.name in found) && !field.checkValidity()) found[field.name] = field.validationMessage;
    }
    setErrors(found);
    const first = Object.keys(found)[0];
    if (first) { form.querySelector(`[name="${first}"]`).focus(); return; }
    setBusy(true);
    setStatus("Sending…");
    try {
      const response = await fetch(ENDPOINT, { method: "POST", headers: { Accept: "application/json" }, body: new FormData(form) });
      const data = await response.json().catch(() => ({}));
      if (data.success === true) {
        form.reset();
        setStatus("Thank you. Your message was sent.");
      } else {
        setStatus(data.message || "Could not send. Please try again.");
      }
    } catch {
      setStatus("Could not reach the server. Check your connection and try again.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <form id="fg-form" action={ENDPOINT} method="POST" onSubmit={onSubmit} noValidate aria-busy={busy}>
      <style>{CSS}</style>
      <h2>{"Get in touch"}</h2>
      <input type="hidden" name="access_key" value={"fk_your_access_key"} />
      <input type="hidden" name="_lang" value={"en"} />
      <div className="fg-field">
        <label htmlFor="fg-name">{"Name"}{" "}<span aria-hidden="true">*</span></label>
        <input type="text" id="fg-name" name="name" autoComplete="name" required aria-describedby="fg-name-error" aria-invalid={errors.name ? "true" : undefined} />
        <p className="fg-error" id="fg-name-error">{errors.name}</p>
      </div>
      <div className="fg-field">
        <label htmlFor="fg-email">{"Email"}{" "}<span aria-hidden="true">*</span></label>
        <input type="email" id="fg-email" name="email" autoComplete="email" required aria-describedby="fg-email-error" aria-invalid={errors.email ? "true" : undefined} />
        <p className="fg-error" id="fg-email-error">{errors.email}</p>
      </div>
      <div className="fg-field">
        <label htmlFor="fg-phone">{"Phone"}</label>
        <input type="tel" id="fg-phone" name="phone" autoComplete="tel" aria-describedby="fg-phone-error" aria-invalid={errors.phone ? "true" : undefined} />
        <p className="fg-error" id="fg-phone-error">{errors.phone}</p>
      </div>
      <div className="fg-field">
        <label htmlFor="fg-subject">{"Subject"}{" "}<span aria-hidden="true">*</span></label>
        <select id="fg-subject" name="subject" required aria-describedby="fg-subject-error" aria-invalid={errors.subject ? "true" : undefined} defaultValue="">
          <option value="">{"Choose…"}</option>
          <option value={"A question"}>{"A question"}</option>
          <option value={"A quote"}>{"A quote"}</option>
          <option value={"Support"}>{"Support"}</option>
        </select>
        <p className="fg-error" id="fg-subject-error">{errors.subject}</p>
      </div>
      <div className="fg-field">
        <label htmlFor="fg-message">{"Message"}{" "}<span aria-hidden="true">*</span></label>
        <textarea id="fg-message" name="message" required aria-describedby="fg-message-error" aria-invalid={errors.message ? "true" : undefined} rows={4} />
        <p className="fg-error" id="fg-message-error">{errors.message}</p>
      </div>
      <div className="fg-trap" aria-hidden="true"><label>Leave this empty <input type="text" name="botcheck" tabIndex={-1} autoComplete="off" /></label></div>
      <button type="submit" disabled={busy}>{"Send"}</button>
      <p className="fg-status" role="status">{status}</p>
      <p className="fg-credit"><a href={"https://formgong.com/en/?ref=badge&utm_source=badge&utm_medium=form&utm_campaign=generator"} target="_blank" rel="noopener">{"Form by Formgong"}</a></p>
    </form>
  );
}
```

A single-file component that does the same as the React one.

``` js
<script setup>
import { onMounted, reactive, ref } from "vue";

const endpoint = "https://formgong.com/submit";
const errors = reactive({});
const status = ref("");
const busy = ref(false);
// Formgong analytics: form views, starts and the field people stop at. No cookies.
// Optional: fg.js counts views, starts and drop-off for your Formgong Analytics page. No cookies.
onMounted(() => { if (!document.querySelector('script[src$="/fg.js"]')) document.head.appendChild(Object.assign(document.createElement("script"), { src: "https://formgong.com/fg.js", defer: true })); });

async function onSubmit(event) {
  const form = event.currentTarget;
  for (const name of Object.keys(errors)) delete errors[name];
  for (const field of form.elements) {
    if (field.name && !(field.name in errors) && !field.checkValidity()) errors[field.name] = field.validationMessage;
  }
  const first = Object.keys(errors)[0];
  if (first) { form.querySelector(`[name="${first}"]`).focus(); return; }
  busy.value = true;
  status.value = "Sending…";
  try {
    const response = await fetch(endpoint, { method: "POST", headers: { Accept: "application/json" }, body: new FormData(form) });
    const data = await response.json().catch(() => ({}));
    if (data.success === true) {
      form.reset();
      status.value = "Thank you. Your message was sent.";
    } else {
      status.value = data.message || "Could not send. Please try again.";
    }
  } catch {
    status.value = "Could not reach the server. Check your connection and try again.";
  } finally {
    busy.value = false;
  }
}
</script>

<template>
  <form id="fg-form" :action="endpoint" method="POST" novalidate :aria-busy="busy" @submit.prevent="onSubmit">
    <h2>Get in touch</h2>
    <input type="hidden" name="access_key" value="fk_your_access_key">
    <input type="hidden" name="_lang" value="en">
    <div class="fg-field">
      <label for="fg-name">Name <span aria-hidden="true">*</span></label>
      <input type="text" id="fg-name" name="name" autocomplete="name" required aria-describedby="fg-name-error" :aria-invalid="errors.name ? 'true' : undefined">
      <p class="fg-error" id="fg-name-error">{{ errors.name }}</p>
    </div>
    <div class="fg-field">
      <label for="fg-email">Email <span aria-hidden="true">*</span></label>
      <input type="email" id="fg-email" name="email" autocomplete="email" required aria-describedby="fg-email-error" :aria-invalid="errors.email ? 'true' : undefined">
      <p class="fg-error" id="fg-email-error">{{ errors.email }}</p>
    </div>
    <div class="fg-field">
      <label for="fg-phone">Phone</label>
      <input type="tel" id="fg-phone" name="phone" autocomplete="tel" aria-describedby="fg-phone-error" :aria-invalid="errors.phone ? 'true' : undefined">
      <p class="fg-error" id="fg-phone-error">{{ errors.phone }}</p>
    </div>
    <div class="fg-field">
      <label for="fg-subject">Subject <span aria-hidden="true">*</span></label>
      <select id="fg-subject" name="subject" required aria-describedby="fg-subject-error" :aria-invalid="errors.subject ? 'true' : undefined"><option value="">Choose…</option><option value="A question">A question</option><option value="A quote">A quote</option><option value="Support">Support</option></select>
      <p class="fg-error" id="fg-subject-error">{{ errors.subject }}</p>
    </div>
    <div class="fg-field">
      <label for="fg-message">Message <span aria-hidden="true">*</span></label>
      <textarea id="fg-message" name="message" required aria-describedby="fg-message-error" :aria-invalid="errors.message ? 'true' : undefined" rows="4"></textarea>
      <p class="fg-error" id="fg-message-error">{{ errors.message }}</p>
    </div>
    <div class="fg-trap" aria-hidden="true"><label>Leave this empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label></div>
    <button type="submit" :disabled="busy">Send</button>
    <p class="fg-status" role="status">{{ status }}</p>
    <p class="fg-credit"><a href="https://formgong.com/en/?ref=badge&amp;utm_source=badge&amp;utm_medium=form&amp;utm_campaign=generator" target="_blank" rel="noopener">Form by Formgong</a></p>
  </form>
</template>

<style>
#fg-form{position:relative;box-sizing:border-box;display:grid;gap:16px;width:100%;max-width:520px;padding:28px;border:1px solid #dbe2ea;border-radius:8px;background:#fff;color:#0f172a;font:16px/1.5 system-ui,sans-serif}
#fg-form *{box-sizing:border-box}
#fg-form h2{margin:0;font-size:24px;line-height:1.3}
#fg-form .fg-field{display:grid;gap:6px;min-width:0;margin:0;padding:0;border:0}
#fg-form label,#fg-form legend{font-weight:500}
#fg-form legend{padding:0;margin-bottom:2px}
#fg-form input,#fg-form select,#fg-form textarea{width:100%;min-height:44px;padding:10px 12px;border:1px solid #64748b;border-radius:8px;background:#fff;color:inherit;font:inherit}
#fg-form textarea{min-height:120px;resize:vertical}
#fg-form .fg-choice{display:flex;align-items:center;gap:10px;font-weight:400}
#fg-form .fg-choice input{width:20px;min-height:20px;margin:0;flex-shrink:0}
#fg-form [aria-invalid=true]{border-color:#b91c1c}
#fg-form .fg-error{margin:0;color:#b91c1c;font-size:14px}
#fg-form .fg-status{margin:0}
#fg-form .fg-credit{margin:0;font-size:13px;text-align:center}
#fg-form .fg-credit a{color:#64748b}
#fg-form .fg-error:empty{display:none}
#fg-form button{min-height:48px;padding:12px 20px;border:0;border-radius:8px;background:#4338ca;color:#fff;font:inherit;font-weight:600;cursor:pointer}
#fg-form button:disabled{opacity:.7;cursor:wait}
#fg-form :focus-visible{outline:3px solid #4338ca;outline-offset:3px}
#fg-form .fg-trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
#fg-form select{appearance:none;-webkit-appearance:none;padding-inline-end:40px;background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) 50%,calc(100% - 15px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}#fg-form select:dir(rtl){background-position:15px 50%,20px 50%}@media (forced-colors:active){#fg-form select{appearance:auto;-webkit-appearance:auto;background-image:none}}
</style>
```

Paste this into Lovable, Bolt, v0, Claude or Cursor. It asks for the same fields, names and checks, and tells the builder not to invent a backend.

```
Add a working form to my site. Match my site's existing design, but keep the field names, order and behaviour below exactly.

Heading: "Get in touch". Submit button: "Send".

Fields, in this order (the name attribute comes first):
1. name: label "Name", short text, required; autocomplete="name"
2. email: label "Email", email address (type=email), required; autocomplete="email"
3. phone: label "Phone", phone number (type=tel), optional; autocomplete="tel"
4. subject: label "Subject", dropdown (select), required; options: "A question", "A quote", "Support"
5. message: label "Message", long text (textarea), required

Sending:
- On submit, send the form with fetch as FormData (not JSON) to POST https://formgong.com/submit with the header Accept: application/json.
- Include two hidden inputs: access_key = fk_your_access_key and _lang = en.
- Load <script src="https://formgong.com/fg.js" defer></script> once on the page. It shows the owner form views, starts and the field visitors stop at, without cookies.
- Add a spam trap: a text input named botcheck, inside a wrapper with aria-hidden="true" that is moved off-screen, with tabindex="-1" and autocomplete="off". Real visitors never see or fill it.
- Show a success message only when the JSON answer has success === true, then reset the form. Otherwise show the answer's message field, or a short generic error. Disable the button while sending.
- Required: directly under the submit button, add a small, muted link to Formgong, exactly <p class="fg-credit"><a href="https://formgong.com/en/?ref=badge&utm_source=badge&utm_medium=form&utm_campaign=generator" target="_blank" rel="noopener">Form by Formgong</a></p>. Keep it visible on the page.

Accessibility:
- Every field has a visible label tied to it with for and id. Radio buttons and checkbox groups sit in a fieldset with a legend.
- Each field has its own error line linked with aria-describedby. Before sending, check validity in the browser and write each invalid field's validationMessage into its error line, set aria-invalid, and focus the first invalid field.

Do not create a backend, an API route, a database table, a server action or an account with another form service. Formgong stores the submission and emails it.
```

| HTML form generators, checked 16 October 2026 |  |  | 
|---|---|---|
|  | This page | Basin HTML form generator | 
|---|---|---|
| How you build | A description, or fields typed by hand | Templates, then add, remove and reorder fields | 
| Output | Plain HTML with a scoped style block, plus framework tabs | Plain HTML for your own styles | 
| Where it posts | Formgong, after you add a key | Basin, after you add an endpoint | 
| Account to copy | None | None | 

## What is in the HTML

- **The form element.**`action` is Formgong's`/submit` address and`method="POST"` . That is all a browser needs to send it.
- **Two hidden inputs.**`access_key` says which form this is.`_lang` sets the language of the thank-you page.
- **Labels.** Every input has a`label` tied to it with`for` and`id` , so a click on the text focuses the field and screen readers read the name.
- **Error lines.** Each field points at its own empty line with`aria-describedby` . When a field is wrong, the script writes the browser's own message there, so a screen reader reads it with the field.
- **Required fields.** The`required` attribute. The browser stops an empty submit before anything is sent.
- **The select.** Its first option is empty, so a required list makes people choose instead of sending the first item by accident.
- **The honeypot.** A`botcheck` input moved off-screen and hidden from assistive tech.
- **A style block.** A short block of CSS, every rule scoped to the form's own id. It cannot restyle the rest of your page.
- **An optional script.** About thirty lines at the end. It keeps the visitor on the page and says “sent” only when Formgong answers`success: true` .

## What makes a generated form actually send

On 2 November 2026 we took the 16 sites on Google's first page for “html form generator” and “contact form generator” in the US, asked each for the same contact form (name, email, message), and pasted the result into an empty page unchanged.

- **0 of 16 gave a form that delivers a message as copied.** 9 showed no code until we made an account. The other 7 posted to`#` , to a made-up`action_page.php` , to a placeholder sentence, or had no`<form>` at all.
- **0 of those 7 added spam protection** : no honeypot, no CAPTCHA.
- **0 of 7 set `autocomplete`** on name or email, so browsers cannot fill them in.
- **2 of 7 failed an axe-core accessibility check** : one had three fields with no label, one had button text below the contrast minimum. 3 of 7 marked no field required.

Our copy does not deliver as pasted either: it carries the placeholder key `fk_your_access_key` until you paste your own free key. Then it sends. We ran axe-core 4.10.2 (WCAG 2.1 A and AA) on each snippet as pasted, with the same page around it.

Five things make the difference, and every copy here has them:

- **A real target.**`action` is Formgong's`/submit` address with`method="POST"` .
- **Your key.** The hidden`access_key` says which form this is. The placeholder`fk_your_access_key` is refused on purpose, so a forgotten key fails loudly instead of losing messages.
- **Field names.** Each input has a`name` , made from its label. Without a name, the browser does not send the value.
- **An honest success message.** The scripts say “sent” only when Formgong answers`success: true` . A wrong key, a full quota or a network error shows a message instead of a fake thank-you.
- **A honeypot.**`botcheck` is off-screen and hidden from screen readers. Simple bots fill it, and Formgong files those posts as spam instead of sending them to you.

## It works without JavaScript

Delete the script and the form still sends. The browser posts the fields. Formgong stores them and answers with a redirect to a thank-you page. It only does that after the submission is saved. A wrong key or a failed check leads to an error page that says what went wrong.

With the script, the visitor stays on your page. It sends the same fields with `fetch`, reads the JSON answer, and shows “sent” only when it says `success: true`. Anything else shows Formgong's own message, such as a wrong key. The [HTML guide](https://formgong.com/en/docs/html/) has the hidden fields Formgong understands, such as `_redirect` for your own thank-you page.

## Styling it yourself

Delete the `<style>` block and the form falls back to your site's CSS. Nothing else depends on it. The field names and the hidden inputs are what make it work, so keep those.

If your site already loads a framework, the class names are one click away. The [Tailwind form generator](https://formgong.com/en/tools/tailwind-form-generator/) and the [Bootstrap form generator](https://formgong.com/en/tools/bootstrap-form-generator/) open on those tabs with the same kind of form.

The HTML goes anywhere you can paste markup: a static site, GitHub Pages, a WordPress Custom HTML block, a Webflow or Framer embed, or a page an AI builder wrote.

## Free limits, email and storage

The HTML is free to copy. Submissions are delivered only after you replace the placeholder key.

Copying the code needs no account. AI generations are limited to 5 an hour and 10 a day for each network, plus 100 a day for the whole site. When a limit is hit, start from a form and edit the fields. Editing runs in your browser and does not call the model.

On the free plan, Telegram is instant and email is sent for every submission. The free plan is 250 submissions a month. Auto-reply is not on the free plan. Submission data is stored in the EU. Support is Formgong at support@formgong.com.

Your description is sent to the model so it can choose fields. It is not stored. Do not paste passwords, card numbers or other secrets.

## Questions

### What is an HTML form generator?

A tool that writes the form element and its fields for you. This one gives plain HTML with labels, required fields and a honeypot, and the action already points to Formgong.

### Is there a free HTML form generator?

Yes, this page. Copying the code is free and needs no account. You need a free Formgong key only when you want the submissions delivered.

### Does the HTML form need JavaScript?

No. The script at the end is optional. Without it, the browser posts the form and Formgong shows a thank-you page, or an error page that says what went wrong.

### Can I use my own CSS?

Yes. Delete the style block and your site's styles apply. Keep the field names, access_key, _lang and botcheck.

### Where do the submissions go?

To your Formgong inbox after you add a real key. On the free plan, Telegram is instant and email is sent for every submission. The free plan is 250 submissions a month.

## Sources

Checked on 20.10.2026 against these public pages: [MDN: <form>](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form), [MDN: <label>](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/label), [Basin HTML form generator](https://usebasin.com/html-form-generator), [Formcarry HTML form generator](https://formcarry.com/html-form-generator), [Formgong HTML guide](https://formgong.com/en/docs/html/).

## See also

[← Formgong](https://formgong.com/en/)
