Changing a form control does not unexpectedly submit or navigate
Why it matters
Selecting a checkbox, radio button, or dropdown option that immediately submits a form or navigates to another page surprises all users — but disproportionately harms keyboard users who may activate controls while scanning options rather than making a final choice. WCAG 2.2 SC 3.2.2 (On Input) is a Level A requirement: changing a form control's value must not automatically cause a context change without warning the user first.
Severity rationale
Low because `onChange`-triggered navigation is only a violation when it directly causes context changes without an explicit submit action — standard controlled state updates do not violate WCAG 2.2 SC 3.2.2.
Remediation
Keep onChange handlers limited to state updates. Require an explicit user action — clicking a Submit or Apply button — before navigating or submitting.
// Bad — selects a filter and immediately navigates
<select onChange={(e) => router.push(`/results?filter=${e.target.value}`)}>
<option value="recent">Most recent</option>
<option value="popular">Most popular</option>
</select>
// Good — updates state; user applies manually
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="recent">Most recent</option>
<option value="popular">Most popular</option>
</select>
<button onClick={() => router.push(`/results?filter=${filter}`)}>Apply filter</button>
If instant filtering is a UX requirement, debounce the update by at least 300ms and announce the result count change via aria-live="polite" so users know the page has updated.
Detection
-
ID:
form-change -
Severity:
low -
What to look for: Enumerate every relevant item. Use form controls like checkboxes, radio buttons, select dropdowns. Change their values and check that no form submission or navigation occurs without explicit action. Look for
onChangehandlers on<select>or<input>elements that callrouter.push(),window.location.href, orform.submit()directly. StandardonChangehandlers that only update state pass by default. -
Pass criteria: At least 1 of the following conditions is met. Changing a form control value does not submit the form or navigate. Submission requires an explicit action (click a button). Applications where
onChangeonly updates state (e.g.,setFilters(e.target.value)) pass this check. -
Fail criteria: An
onChangehandler on a checkbox, select, or radio button directly triggers form submission or navigation without an explicit submit button click. -
Skip (N/A) when: The project has no form controls.
-
Detail on fail: Example:
"Selecting a filter checkbox automatically applies the filter and navigates. Form submits on radio button change without submit button click" -
Remediation: Use onChange to update state, not submit:
// Good <select value={filters} onChange={(e) => setFilters(e.target.value)}> {/* options */} </select> <button onClick={() => applyFilters()}>Apply</button> // Or use onChange with debounce if auto-apply is needed useEffect(() => { const timer = debounce(() => applyFilters(), 300) return () => clearTimeout(timer) }, [filters])
External references
- wcag:2.2 · 3.2.2 — On Input
- section-508:2018-refresh · 501.1 — Scope
Taxons
History
- 2026-04-18·v1.0.0·Initial import from accessibility-wcag·automated