Field metadata carries more than a name. getInputProps derives the name, the id, the form id, required, the default value, aria-invalid, and the aria-describedby that points at the error message. Passing name="email" by hand gets you one of those seven and drops the rest — and because the field still submits, nothing looks broken. What breaks is invisible: the control no longer announces its error, no longer repopulates after a failed submit, and no longer resets with the form.
Per-field useState is the other half of the same mistake. The form already tracks every value, its dirty state, and its errors; a second copy in component state disagrees with the first the moment anything non-trivial happens — a server-side rejection that should refill the form, a reset, a default arriving from a loader. The bug always surfaces later than the code that caused it.
Conform itself permits the manual form — its docs read fields.email.name and fields.email.initialValue off the metadata directly, and the tutorial only reaches for the helpers at the end, to "minimize the boilerplate" for native inputs. That is the honest status of this rule: manual access is legal Conform, and it is fine as long as you also wire aria-invalid and aria-describedby yourself. This rule exists because that last part is the part everyone skips, and because ui-lib ships components where the wiring is already done.
Twenty-nine of the library's components ship a Conform-bound variant — every control that produces a submittable value. That is deliberate: this rule used to send you to "bind it by hand" for most of the library, which is the path the rule itself calls the failure mode. What is left without a variant is a sub-part of another control (a ColorArea channel, a ListBox, a TagGroup) rather than a field of its own; bind those yourself with getInputProps, or with useControl when the control has no native form value. That is not a worse path — it is the same metadata, read explicitly.
Reading the metadata explicitly does not mean spreading getInputProps onto a react-aria control. The helper returns the DOM names — required, defaultChecked, min, max — and react-aria takes isRequired, defaultSelected, minValue, maxValue. A JSX spread skips excess-property checking, so the mismatched half is dropped by filterDOMProps with no type error, no React warning, and no attribute left in the DOM: a Switch renders off after a failed submit, a NumberField silently forgets the schema's bounds. Spread getInputProps onto a real <input>; onto a component, name the props.