splitforms.com
← Back to the journal

Form submission canceled because the form is not connected (Fix)

Fix the Chrome warning 'Form submission canceled because the form is not connected': createElement forms, React/Vue unmounts and modal buttons.

Start free — 500 submissionsSee pricing →No card, no time limit. Pro is $5 per month.
Short answer
The browser only submits a <form> that is inside the document. The warning appears when the form was never appended (built with document.createElement) or was removed before submission (a React, Vue or Svelte re-render, a closing modal, a wizard step). Fix it by appending the form first, or by calling e.preventDefault() and sending FormData with fetch before you change the UI.
Try it on your own form

Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.

Create free account

What the warning means

Open the console and you see: Form submission canceled because the form is not connected. Under the HTML spec's form submission algorithm, the browser first checks that the <form> element is connected, meaning it sits inside a document. If it is not, the submission stops right there: no request, no navigation, no error thrown to your code, just this console message.

So the question is always the same: at the moment the browser tried to submit, where was the form? Either it was never inserted, or something removed it. You can check with form.isConnected. The four causes below cover almost every case.

Cause 1: a form built with document.createElement and never appended

A common trick for a POST download or a redirect to a payment page is to build a form in JavaScript and call submit(). If you skip the append, the form lives only in memory:

// Broken: the form is not in the document
const form = document.createElement("form");
form.method = "POST";
form.action = "/export";
const input = document.createElement("input");
input.name = "id";
input.value = "42";
form.appendChild(input);
form.submit(); // Form submission canceled because the form is not connected

Fix: append it before submitting, then remove it if you want to keep the DOM clean.

document.body.appendChild(form);
form.submit();
form.remove();

If you do not actually need the browser to navigate, drop the form entirely and send the data with fetch. A FormData object works without any element in the page:

const data = new FormData();
data.append("id", "42");
await fetch("/export", { method: "POST", body: data });

The difference between submit() and requestSubmit() (events and validation) is covered in form.submit() vs requestSubmit(). Both need a connected form.

Cause 2: React, Vue or Svelte removes the form before the browser submits

This is the most common source of the warning. The click on a submit button runs your handler first, and only afterwards does the browser start the native submission. If the handler changes state so the component re-renders without the form (for example swapping in a "thank you" view), the form is gone by the time the browser gets to it.

// Broken (React): the form unmounts before the native submit
function Contact() {
  const [sent, setSent] = useState(false);
  if (sent) return <p>Thanks!</p>;
  return (
    <form action="/contact" method="POST">
      <input name="email" type="email" required />
      <button type="submit" onClick={() => setSent(true)}>Send</button>
    </form>
  );
}

Fix: handle the submit event, call e.preventDefault(), send the data yourself, and change state only after the request finishes.

function Contact() {
  const [sent, setSent] = useState(false);

  async function onSubmit(e) {
    e.preventDefault();                       // stop the native submit
    const form = e.currentTarget;
    const res = await fetch(form.action, {
      method: "POST",
      body: new FormData(form),
    });
    if (res.ok) setSent(true);                // now it is safe to unmount
  }

  if (sent) return <p>Thanks!</p>;
  return (
    <form action="/contact" method="POST" onSubmit={onSubmit}>
      <input name="email" type="email" required />
      <button type="submit">Send</button>
    </form>
  );
}

The same rule applies in Vue (@submit.prevent, then flip a v-if after fetch) and Svelte (call preventDefault() in the submit handler, then change the {#if} state). While the request is in flight, disable the button so a second click does not send twice; see preventing duplicate form submissions.

Cause 4: type=submit on a button that also closes a dialog

A <button> inside a form defaults to type="submit". Put a "Cancel" or "Close" button in a form inside a dialog, wire its click to close and remove the dialog, and the browser tries to submit a form that has just been removed. Two clean fixes:

<!-- Buttons that only close: not a submit button -->
<button type="button" onclick="dialog.close()">Cancel</button>

<!-- Or let the dialog close itself, without removing the form -->
<dialog id="dialog">
  <form method="dialog">
    <button value="cancel">Cancel</button>
  </form>
</dialog>

method="dialog" closes the dialog and keeps the form in the document, so nothing is canceled. If your framework removes the dialog from the DOM when it closes, do that after the dialog's close event, not inside the button's click handler.

Quick debugging checklist

  1. Right before the submit, log form.isConnected. false confirms this bug.
  2. Search for document.createElement("form") and check that appendChild runs before submit().
  3. Look at what the submit button's onClick does. Any state change that hides the form belongs after the request, not before it.
  4. Check every <button> inside the form has an explicit type. Only the real send button should be type="submit".
  5. In React DevTools or Vue DevTools, watch whether the form component unmounts on click.
  6. Still not sending, but no warning? Work through why your HTML form is not submitting (missing name attributes, buttons outside the form, wrong method).

Or skip the native submit and POST to a form endpoint

Most of these bugs disappear when the browser never runs a native submission at all. Cancel it, send FormData with fetch, and update the UI when the response arrives. Here is the pattern pointed at the splitforms endpoint:

<form id="contact" action="https://splitforms.com/api/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
  <p id="status" role="status"></p>
</form>

<script>
const form = document.getElementById("contact");
const status = document.getElementById("status");

form.addEventListener("submit", async (e) => {
  e.preventDefault();                 // no native submit, nothing to cancel
  status.textContent = "Sending...";
  const res = await fetch(form.action, {
    method: "POST",
    headers: { Accept: "application/json" },
    body: new FormData(form),
  });
  const result = await res.json();
  if (res.ok && result.success === true) {
    form.reset();                     // safe to hide or remove the form now
    status.textContent = "Thanks! Your message was sent.";
  } else {
    status.textContent = result.message || "Could not send. Please try again.";
  }
});
</script>

Status codes and error bodies are listed in the response guide. Get a free access key.

FAQ

What does 'Form submission canceled because the form is not connected' mean?

The browser was asked to submit a form that is no longer (or never was) part of the document. The HTML spec says a form that is not connected to a document cannot be submitted, so Chrome cancels the navigation and logs this warning instead. Nothing is sent to the server.

Why do I see this warning in React?

Usually a click or submit handler changes state (setSubmitted(true), closing a modal, moving to the next step) and React removes the <form> before the browser runs its native submission. Call e.preventDefault() in onSubmit, send the data with fetch and FormData, and only change state after the request finishes.

How do I submit a form created with document.createElement?

Append it to the page first: document.body.appendChild(form), then form.submit(), then optionally form.remove(). A form that is only held in a JavaScript variable is not connected, so submit() is canceled. If you do not need a page navigation, skip the form and POST a FormData object with fetch.

How can I check whether a form is connected?

Read form.isConnected in the console or in your handler. It is true when the element is inside a document and false when it was never inserted or has been removed. Logging it right before the submit call shows whether your code removes the form too early.

References: HTML spec: form submission algorithm and MDN: Node.isConnected.

Related articles

More practical guidance from tutorials.

Browse the journal →
Tutorials

How to Remove the Arrows From a Number Input (CSS, Tailwind, React)

Hide the number input spin buttons with ::-webkit-inner-spin-button and appearance: textfiel…

7 min readRead →
Tutorials

How to Change the Autofill Background Color of an Input (CSS, Tailwind)

Remove Chrome's yellow autofill background with input:-webkit-autofill, inset box-shadow, -w…

7 min readRead →
Tutorials

input type=number maxlength Not Working? 4 Fixes That Work

Why maxlength is ignored on input type=number, plus four fixes: min/max, oninput slice, text…

8 min readRead →

Explore this topic

Start with the overview, then move into focused guides.

OverviewWhat Is a Form Backend? (Complete Guide)Start here →GuideSelf-Hosted vs SaaS Form BackendRead →GuideForm Backend vs Form BuilderRead →GuideWhere Do Form Submissions Go?Read →ReferenceForm backend guideOpen →

Building forms with ChatGPT, Claude, Cursor, or v0? Connect the native MCP server and give your agent a production form backend.

Explore the MCP server →

Give your form a production backend.

One endpoint adds delivery, spam filtering, storage, and integrations. Start with 500 free submissions — no card, no time limit.

Create free accountRead the docs →
Secure checkoutSSL encryptionPrivacyProtected
VISAAMERICANEXPRESSstripe