splitforms.com
← Back to the journal

HTML Form Not Submitting? 8 Markup Bugs and Fixes (2026)

HTML form not submitting? The 8 markup-level causes: missing action or method, wrong button type, nested forms, hidden required fields, disabled inputs,…

Start free — 500 submissionsSee pricing →No card, no time limit. Pro is $5 per month.
Short answer
An HTML form that won't submit has a markup bug in one of 8 places: missing/wrong action, missing method="POST", type="button" on the submit button, the button outside the form tag, nested <form> elements (invalid — the browser drops the inner one), a required field that's hidden, disabled inputs being excluded from the payload, or JavaScript calling preventDefault() without sending the data itself. Inspect the parsed DOM in DevTools — not your source file — to find which one.
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

Start from a known-good reference form

Before debugging, know what correct looks like. This is the minimal HTML form that submits in every browser with zero JavaScript:

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

Five load-bearing parts: an action URL that accepts POST, method="POST", a name on every input, a type="submit" button inside the form, and no other form wrapped around it. Every bug below breaks one of those five. (For a broader debugging flow that includes network and email failures, see why is my contact form not working — this post stays at the markup layer.)

Bug 1 & 2: missing action or method

With no action, the browser submits to the current page URL. With no method, it defaults to GET. Combine the two defaults and submitting your form just reloads the page with the data in the query string:

https://yoursite.com/contact?name=Jane&email=jane%40example.com&message=Hi

That URL appearing after submit is the diagnostic signature. It means the form "worked" in browser terms — it just GET-requested your own static page, which threw the data away. The fix is both attributes:

<form action="https://splitforms.com/api/submit" method="POST">

Two subtleties worth knowing. Relative actions resolve against the current URL: action="submit" on the page /contact/ posts to /contact/submit, but on /contact (no trailing slash) it posts to /submit. Use absolute URLs and this class of bug disappears. And method is case-insensitive but the value set is tiny: only get, post, and dialog are valid; anything else (like method="PUT") silently falls back to GET. The full attribute contract is in our HTML form action guide and the action attribute reference.

Bug 3: the button isn't a submit button

Inside a form, a bare <button> defaults to type="submit". The problem is the other two values:

<button type="button">Send</button>  <!-- never submits -->
<button type="reset">Send</button>   <!-- clears the form (!) -->
<button>Send</button>                 <!-- submits (default) -->
<button type="submit">Send</button>   <!-- submits (explicit, preferred) -->

type="button" sneaks in two ways: copied from a UI-library example (component libraries default their Button to type="button" precisely to avoid accidental submits — MUI, Chakra, and shadcn/ui all do this when used a certain way, so check the rendered DOM), or added by a developer who wired a click handler and forgot the handler has to do the submitting now. Also check the button isn't disabled — a common React bug leaves disabled={isSubmitting} stuck true after a failed attempt.

Bug 4: the button is outside the form

A submit button only submits the form it belongs to. Belonging is determined by DOM ancestry — or by an explicit form attribute:

<!-- Broken: button is a sibling, not a child -->
<form id="contact" action="..." method="POST">...</form>
<button type="submit">Send</button>

<!-- Fixed without moving it: associate by id -->
<form id="contact" action="..." method="POST">...</form>
<button type="submit" form="contact">Send</button>

This happens constantly in component-based codebases where the button lives in a footer/toolbar component rendered outside the form component. The form="id" attribute is the standards-compliant fix and works in every modern browser.

Bug 5: nested forms (the browser deletes one)

HTML forbids a <form> inside a <form>, and the failure mode is brutal: the parser drops the inner form tag at parse time. Your source shows two forms; the DOM has one. The inner form's inputs become children of the outer form, and the inner submit button now submits the outer form — to the wrong action.

Typical scenarios: a newsletter embed pasted inside a page-wide form, a search widget inside a checkout form, or a CMS/legacy framework (ASP.NET WebForms) that wraps the entire body in <form runat="server">.

Diagnose against the parsed DOM, never your source file:

// Run in the console — should list each form with the action you expect
document.querySelectorAll("form").forEach(f => console.log(f.id || "(no id)", f.action));

// And confirm your input belongs to the form you think it does:
document.querySelector("[name=email]").form.action

The fix is structural: move one form out, or replace the inner form with a form-attribute-associated group of inputs pointing at their own form element placed elsewhere in the DOM.

Bug 6: hidden required fields block submission silently

Constraint validation runs before submission. If any control fails — empty required, pattern mismatch, bad type="email" value — the browser cancels the submit and focuses the first invalid field. But if that field is hidden (display:none, a collapsed accordion step, an inactive tab in a multi-step form), the browser can't focus it, shows nothing, and the form just doesn't submit. The only evidence is a console warning:

An invalid form control with name='phone' is not focusable.

Fixes, pick one: remove required from fields hidden at submit time; toggle the disabled attribute on hidden steps (disabled controls skip validation and submission — see bug 7); or validate in JS and set novalidate on the form. To quickly test whether validation is what's blocking you, run form.reportValidity() in the console — it returns false and highlights the offender.

Bug 7: disabled fields vanish from the payload

Per spec, disabled controls are excluded from form serialization. A form that submits fine but arrives missing fields often has this bug — someone used disabled to make a pre-filled field uneditable:

<input name="plan" value="pro" disabled />   <!-- sends nothing -->
<input name="plan" value="pro" readonly />   <!-- sends plan=pro -->

readonly keeps the value in the submission. Caveat: readonly doesn't exist for <select>, checkboxes, or radios — for those, keep the control enabled and enforce server-side, or duplicate the value into a hidden input. While you're here, remember unchecked checkboxes also send nothing at all (not false — literally absent), which surprises every backend developer once.

Bug 8: preventDefault without a follow-through

The final markup-adjacent bug: a script intercepts the submit event, cancels the native submission, and then fails to send the data itself.

form.addEventListener("submit", (e) => {
  e.preventDefault();          // native submit cancelled
  if (!validate()) return;     // validate() throws or always returns false
  send(form);                  // never reached
});

Variants of the same trap: a legacy onsubmit="return validate()" where validate returns undefined (falsy → cancelled); a third-party script (analytics wrapper, anti-bot widget) that hooks submit globally; or jQuery's e.stopImmediatePropagation() preventing your handler from ever running. To find hidden handlers: Elements panel → select the <form> → Event Listeners tab → expand submit. Temporarily test the raw markup by running form.submit() in the console — .submit() bypasses both submit handlers and validation, so if it works, your markup is fine and the bug is in a script. The difference between an inline return false and event.preventDefault() is explained in return false vs preventDefault().

Why submit() skips your handler while requestSubmit() does not is explained in form.submit() vs requestSubmit().

Once it submits: where does the data go?

Fixing the markup gets the request out of the browser — then it needs a server that accepts POST. If you're on a static host, there isn't one (that's the 405 error). The no-backend answer is a form endpoint: splitforms accepts standard HTML form posts at https://splitforms.com/api/submit, emails you each submission, filters spam, and logs everything in a dashboard. Free for 500 submissions total — start from the free contact form template or generate markup with the HTML form generator, then verify end-to-end with the pre-launch test checklist.

Other form problems and fixes

Still stuck? Test your endpoint with our free Form Action Tester or contact splitforms support.

FAQ

What happens if an HTML form has no action attribute?

The browser submits to the current page's URL. That's valid HTML — the form does submit — but unless the current URL has a server handler that accepts POST, you get the page reloading with nothing happening, or a 405 error. If you see your own page reload with form values in the query string, your form is also missing method="POST" and defaulted to GET. Always set both: action pointing at a real endpoint and method="POST".

Why doesn't my submit button submit the form?

Check three things in order. First, the button's type: <button type="button"> never submits — it must be type="submit" or have no type attribute while inside the form. Second, position: the button must be inside the <form> element (or reference it via form="form-id"). Third, JavaScript: a click or submit handler calling preventDefault() (or returning false in legacy onsubmit code) cancels the native submission. Inspect the button in DevTools and check the Event Listeners panel for handlers you didn't expect.

Can you nest one HTML form inside another?

No. Nested forms are invalid HTML, and the browser's parser silently drops the inner <form> tag at parse time. The remaining inputs get adopted by the outer form or orphaned entirely, so submit buttons stop working in confusing ways. This bites people who paste a form widget (search box, newsletter embed) inside an existing form, or whose CMS wraps the whole page body in a form (classic WebForms). Check with document.querySelectorAll('form') and inspect the parsed DOM — not your source — in the Elements panel.

Why are disabled input values not submitted?

By spec, disabled controls are excluded from form serialization — their name=value pairs never enter the request body. If you disable fields to make them read-only, the server receives nothing for them. Use the readonly attribute instead, which keeps the field non-editable but still submits its value. Note readonly doesn't apply to <select> or checkboxes; for those, keep them enabled and enforce on the server, or mirror the value in a hidden input.

Why does pressing Enter not submit my form?

Implicit submission (Enter in a text field) requires the form to have a submit button, or to contain only a single text-type input. If your form has multiple text fields and no <button type="submit"> or <input type="submit">, Enter does nothing in most browsers. Also, if the first submit-type element in DOM order is a button you've wired to something else, Enter triggers that one. Add a real submit button — visually hidden if necessary — and Enter works.

Do I need JavaScript to submit an HTML form?

No. A plain <form action="..." method="POST"> with named inputs and a submit button is a complete, working submission mechanism — browsers have shipped it since the 1990s. JavaScript is only needed if you want to stay on the page (AJAX), do custom validation, or transform data before sending. If your no-JS form doesn't submit, the bug is in the markup, which is exactly what this post catalogs.

Where do I point my form's action if I don't have a server?

Use a hosted form endpoint. splitforms gives you a URL that accepts standard HTML form posts: set action="https://splitforms.com/api/submit" method="POST", include a hidden access_key input from your dashboard, and name every field. Submissions are emailed to you and logged in a dashboard with spam filtering built in. The free plan covers 500 submissions total, which is more than most contact forms see in a year.

Need an endpoint that actually accepts POST? Get a free splitforms access key and point your form at https://splitforms.com/api/submit.

Related: full contact form debugging guide, the form resubmission popup, complete HTML contact form code, and the splitforms docs.

Seeing "Form submission canceled because the form is not connected" in the console? The form was removed (or never added) before it submitted; see how to fix the form not connected warning.

Form submits when you press Enter and you don't want it to? See how to stop the Enter key submitting a form.

HTML reference: the form action attribute, form method (GET vs POST), the <button> element and the required attribute.

Related articles

More practical guidance from guides.

Browse the journal →
Guides

Contact form not working? 8 common causes (and fixes)

From CORS errors to silent SMTP failures, the most common reasons a contact form silently br…

9 min readRead →
Guides

Gravity Forms Not Sending Email? 5 Fixes That Work (2026)

Gravity Forms notifications not arriving? Find the wp_mail cause in 2 minutes and fix it wit…

7 min readRead →
Guides

Form Backend Migration Checklist: Fields, Files and Email

Move an existing form to SplitForms with a practical checklist for field mapping, redirects,…

6 min readRead →

Explore this topic

Start with the overview, then move into focused guides.

OverviewContact Forms for Every FrameworkStart here →GuideContact Form for Next.jsRead →GuideContact Form for ReactRead →GuideContact Form for VueRead →ReferenceContact form 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