base-ui icon indicating copy to clipboard operation
base-ui copied to clipboard

[checkbox] off state doesn't match native behavior in forms

Open Malien opened this issue 2 months ago • 3 comments

Here's the repro: https://codesandbox.io/p/sandbox/base-ui-beta-4-checkbox-inconsistency-th4xrk

The issue is simple:

<form action={console.log}>
  <input type="checkbox" name="vanila" value="42" defaultChecked />
  <input type="checkbox" name="vanila" value="69" />

  <Checkbox.Root name="base-ui" value="42" defaultChecked />
  <Checkbox.Root name="base-ui" value="69" />
</form>

Submitting this form will result with values: [["vanila", "42"], ["base-ui", "42"], ["base-ui", "off"]]. This is inconsistent with the HTML spec, where unchecked checkboxes submit no values.

Moreover, react 19's form action pattern will reset the form to their default values. Submitting the same form for a second time will yield the desired outcome of [["vanila", "42"], ["base-ui", "42"]].


I would desire consistency with the html checkboxes, so that unchecked values do not appear in form submission.

Malien avatar Nov 17 '25 14:11 Malien

Submitting the same form for a second time will yield the desired outcome of [["vanila", "42"], ["base-ui", "42"]]

@Malien How do you reproduce this part in CSB? I clicked submit more than once but it stays at base-ui: ["42","off"]

I guess it makes sense to match the native behavior, but in your application you could technically just ignore/discard the 'off' value and it shouldn't cause other issues?

mj12albert avatar Nov 17 '25 16:11 mj12albert

Probably this is an opinionated thing we're doing to match Switch and avoid having a 'on' value without a corresponding 'off'? https://github.com/mui/base-ui/blob/158aa3a1607f93065724d0f46e77e4cf53a05909/packages/react/src/checkbox/root/CheckboxRoot.tsx#L323-L325

mj12albert avatar Nov 17 '25 16:11 mj12albert

in your application you could technically just ignore/discard the 'off'

Yes, right now I do discard the 'off' values. It is somewhat unintuitive though for cases where a set of checkboxes behave basically like a <select multiple>.


How do you reproduce this part in CSB?

My bad. The steps turned out to be more involved:

  • submit the form (["42", "off"])
  • tick the second base-ui checkbox
  • submit the form (["42", "69"])
  • untick the second base-ui checkbox
  • submit the form (["42", "off"])
  • submit the form again (["42"])

If I were to skip any of the steps, it doesn't seem to behave this way.


Probably this is an opinionated thing we're doing to match Switch and avoid having a 'on' value without a corresponding 'off'?

Yeah... I can see that. Switches behaving like that honestly makes sense..

<h1>Settings</h1>
<label>
  Airplane mode
  <Switch name="airplane-mode" />
</label>
<label>
  Wi-Fi
  <Switch name="wifi" defaultChecked />
</label>

// airplane-mode: "off", wifi: "on"

Well. I'd argue up until you specify a value.

<h1>Toppings</h1>
<Switch name="topping" value="pepperoni" />
<Switch name="topping" value="pineapple" />
<Switch name="topping" value="extra-cheese" />

One behaves like a map { airplane: "on" | "off", wifi: "on" | "off" } Another one behaves like a bag of options ["pepperoni", "extra-cheese"] (where ["off", "pineapple", "off"] is a weird value to receive)

I don't think the off value changing based on the presence of the value prop is a good solution. It feels "magicky" in that no-one would expect a behavior change based on that.

Personally, I am impartial to the way html checkboxes work already. Equating formData.get("wifi") == null with an "off" option is a good solution, and is consistent in the way html form elements work in general (you can just imagine an <input type="checkbox" /> just having a default value of "on", which you can override).

I see the appeal of the switch example. I see the want of making Switch and Checkbox behave the same. I don't find an explicit "off" option convenient or necessary.

Malien avatar Nov 18 '25 10:11 Malien

This fix will be available in the next npm release of Base UI.

In the meantime, you can try it out on our Canary release channel:

npm i https://pkg.pr.new/@base-ui-components/react@3406

github-actions[bot] avatar Dec 04 '25 08:12 github-actions[bot]