Fix a form that submits when a toolbar button is clicked

A user clicks “Show password” inside a signup form and receives validation errors. Clicking a formatting button saves an unfinished draft. The React handlers may be correct: the browser is also performing the button's native default action.
A button associated with a form can submit it unless its type specifies otherwise. The smallest fix is often one explicit attribute, followed by a focused interaction check. This article uses an illustrative signup form and explains why native semantics matter even when a framework manages the fields.
Reproduce the native behavior
Click the toolbar button while the form contains invalid fields, then inspect whether the submit handler or browser validation runs. Use the network panel to determine whether a request was sent. Native validation can block submission before the application handler, so a missing request does not mean the button had no submit behavior.
Inspect the rendered DOM, not only the component call. A shared Button component may omit type or force submit internally. The final button element determines browser behavior. Follow the prop through the wrapper before changing the parent form.
Check form association. A button can belong to a form through nesting or an explicit form attribute. A visually separate toolbar may still submit a form elsewhere on the page. Layout position is not the same as semantic ownership.
State each button's purpose explicitly
Use type="button" for actions such as toggling visibility, adding a local row, opening a help panel, or applying formatting. Use type="submit" for the action that intentionally submits the form. Use reset only when its native reset behavior matches the product.
The following illustrative fragment shows the distinction. It assumes togglePassword changes visibility and handleSubmit owns the intended request lifecycle.
<form onSubmit={handleSubmit}>
<input name="password" type={visible ? "text" : "password"} />
<button type="button" onClick={togglePassword}>
{visible ? "Hide password" : "Show password"}
</button>
<button type="submit">Create account</button>
</form>
This change preserves native submission and keyboard behavior. It avoids adding a special case to the submit handler merely to guess which unintended control caused it.
Do not put every action on a div to escape form behavior. Native buttons provide keyboard activation and familiar semantics. Correctly specifying the button's purpose is simpler than recreating those properties manually.
Inspect shared component defaults
A design-system Button can choose a default type, but the decision should be explicit and documented. Some systems default to button to reduce accidental submission; others require callers to specify intent. Either approach needs a way to render an actual submit control.
Ensure wrapper props are forwarded correctly. If type is destructured but never passed to the DOM element, callers cannot fix the problem. If a polymorphic component renders a link, type no longer has the same meaning. Review the actual element and supported props.
Changing a shared default affects all callers. Audit forms that relied on the previous implicit submit behavior. A local attribute fix is often safer for an isolated bug, while a shared correction can be justified when the same omission recurs across the application.
Preserve intentional keyboard submission
Test pressing Enter in ordinary text fields. Users often expect it to submit a simple form. A fix that prevents all default events may stop accidental toolbar submission while breaking intentional keyboard use.
Keep submission centralized in the form's onSubmit path where appropriate. A submit button's click handler alone can miss keyboard or programmatic submission paths. The form handler can prevent navigation, validate application rules, and start the request.
Avoid indiscriminate event stopping. preventDefault cancels a default action; stopPropagation controls event bubbling. They solve different problems. Neither should become a substitute for correct button semantics when the control is not intended to submit in the first place.
Check validation and pending state separately
Once the accidental trigger is fixed, confirm the intended submit path still handles invalid input and duplicate clicks. Browser validation and application validation may both participate. The product should make their messages and timing understandable.
A password toggle should not clear field values, change dirty state unexpectedly, or move focus unnecessarily. The fix is small, but its surrounding interaction can still contain defects. Test that the user can toggle and continue typing without losing context.
Disable or otherwise guard the submit action during the operation according to the existing contract. Do not disable every toolbar control automatically if users should still be able to inspect their input. Pending behavior should follow the product journey, not a broad workaround for the original bug.
Use AI to audit form semantics
Give an assistant the form and shared Button implementation. Ask it to list every rendered button, its native type, form association, and intended action. This is a useful bounded review that can find repeated omissions.
An illustrative request is: “Find controls in this form that can submit unintentionally. Trace wrapper props to the DOM. Preserve Enter submission and native button keyboard behavior. Propose the smallest attribute or forwarding fix.” This keeps the assistant from rewriting the entire form library.
Verify each finding in the browser or rendered DOM. A component named IconButton might render a button, a span, or a link. The guide on frontend behavior beyond screenshots expands this semantic review to other interactions.
Verify the correction and its limits
Test pointer and keyboard activation of the toolbar action. Assert that it changes its intended local state without submitting or displaying unrelated validation. Then activate the actual submit button and press Enter from a field.
Inspect request count and preserved input. Add a focused regression test if the shared wrapper or a critical form caused the problem. The assertion should protect behavior: the toolbar does not submit, while the intended path does.
Explicit types fix the native default-action defect. They do not prove that validation, authentication, or server authorization is correct. Keeping the conclusion narrow makes the change easy to review and prevents a simple browser-semantic issue from becoming an unnecessary state-management rewrite.
For icon-only controls, provide an accessible name that describes the action. The password toggle should convey whether activating it will show or hide the value, and its state should remain understandable without relying on the icon alone. A type attribute prevents accidental submission but does not supply that name. Check the accessibility tree as well as the DOM attributes, especially when a shared wrapper inserts labels or renders the icon through another component.
Inspect controls added through portals or associated forms as well as visible descendants. A dialog footer may render outside the form’s DOM subtree while using the form attribute to submit it. That association can be intentional and should remain intact for the real Save action. The audit should therefore classify ownership and purpose together, rather than assuming every button outside the form is harmless or every button inside it should submit. Explicit semantics make both the local toolbar and the separated footer predictable.
Sources
MDN: button element documents button types and form association.
React: You Might Not Need an Effect explains placing user-triggered actions in event handlers.
About the Author

Kamruz
Full Stack Developer
A passionate developer who loves creating modern web applications and sharing knowledge with the community.
More About Me