remix-run/react-router · error
Cannot submit a or <input type="submit"> without a
Error message
Cannot submit a <button> or <input type="submit"> without a <form>
What it means
The DOM interceptor in @react-router/lib/dom/dom.ts handles submit events for router-aware forms. When the submit target is a <button> or an <input type="submit|image">, it reads target.form to inherit action/method/enctype from the associated form; if the control has no form (not nested in one and no valid form="id" attribute), target.form is null and it throws immediately — you cannot build a submission without a form.
Solutions
- Wrap the control in a <form> — preferably react-router's <Form> or <form method="post">
- Point the control at an existing form via <button form="form-id">
- If the button isn't meant to submit anything, give it type="button"
Example fix
// before
<button type="submit">Go</button> // no <form> ancestor
// after
import { Form } from "react-router";
<Form method="post">
<button type="submit">Go</button>
</Form> Defensive patterns
Strategy: type-guard
Validate before calling
// guard custom submit handling before reacting
document.addEventListener("submit", (e) => {
const t = e.target;
const associated =
t instanceof HTMLFormElement ||
((t instanceof HTMLButtonElement || t instanceof HTMLInputElement) && t.form != null);
if (!associated) return; // ignore stray submit events
}); Type guard
function isFormAssociatedSubmit(
target: EventTarget | null,
): target is HTMLButtonElement | HTMLInputElement {
return (
(target instanceof HTMLButtonElement || target instanceof HTMLInputElement) &&
target.form != null
);
} Prevention
- Always author submit controls inside <Form>/<form>
- Prefer <button form="existing-id"> over moving buttons out of forms
- Use type="button" for controls that only trigger JS handlers
- Watch for portal/refactor changes that detach buttons from their form subtree
When it happens
Trigger: A click/submit on <button type="submit"> that is not inside a <form>; a control whose form attribute references a non-existent or detached form id; the wrapping form being removed from the DOM before the event fires.
Common situations: Component refactor moves a button out of its form; the form="some-id" attribute has a typo'd or stale id; conditional rendering removes the <form> while keeping the button; portaling the button outside the form subtree.
Related errors
- Cannot submit element that is not
- Cannot handle form actions without a decodeAction function
- React Router Vite plugin can't detect preamble. Something…
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/2d3ac51ade5bd0f5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router/lib/dom/dom.ts:300
if (isFormElement(target)) {
// When grabbing the action from the element, it will have had the basename
// prefixed to ensure non-JS scenarios work, so strip it since we'll
// re-prefix in the router
let attr = target.getAttribute("action");
action = attr ? stripBasename(attr, basename) : null;
method = target.getAttribute("method") || defaultMethod;
encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
formData = new FormData(target);
} else if (
isButtonElement(target) ||
(isInputElement(target) &&
(target.type === "submit" || target.type === "image"))
) {
let form = target.form;
if (form == null) {
throw new Error(
`Cannot submit a <button> or <input type="submit"> without a <form>`,
);
}
// <button>/<input type="submit"> may override attributes of <form>
// When grabbing the action from the element, it will have had the basename
// prefixed to ensure non-JS scenarios work, so strip it since we'll
// re-prefix in the router
let attr = target.getAttribute("formaction") || form.getAttribute("action");
action = attr ? stripBasename(attr, basename) : null;
method =
target.getAttribute("formmethod") ||
form.getAttribute("method") ||
defaultMethod;
encType =
getFormEncType(target.getAttribute("formenctype")) ||View on GitHub (pinned to 6beaca3952)