# Changing a form control does not unexpectedly submit or navigate

- **Pattern:** `ab-000119` (`accessibility-wcag.understandable.form-change`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000119
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000119)

## 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.

```tsx
// 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 `onChange` handlers on `<select>` or `<input>` elements that call `router.push()`, `window.location.href`, or `form.submit()` directly. Standard `onChange` handlers 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 `onChange` only updates state (e.g., `setFilters(e.target.value)`) pass this check.
- **Fail criteria:** An `onChange` handler 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:

  ```tsx
  // 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 3.2.2
- section-508 501.1

Taxons: accessibility

HTML version: https://auditbuffet.com/patterns/ab-000119
