Skip to content

feat(core): native form participation for custom inputs via htmlName#3558

Open
arham766 wants to merge 1 commit into
facebook:mainfrom
arham766:feat/native-form-participation
Open

feat(core): native form participation for custom inputs via htmlName#3558
arham766 wants to merge 1 commit into
facebook:mainfrom
arham766:feat/native-form-participation

Conversation

@arham766

@arham766 arham766 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the form serialization strategy from #3343 (Tier 3): "hidden native input pattern across Selector, MultiSelector, Tokenizer, Slider, Switch, Checkbox, RadioList".

Today none of these seven components can participate in a native <form> — none accepts a name, so FormData/submission silently drops them. This PR gives all seven the same htmlName prop TextInput/NumberInput already have, with serialization that mirrors what the equivalent native control would do:

Component Mechanism Serializes as
Switch / CheckboxInput name on the existing native checkbox "on" when checked, absent otherwise (native checkbox semantics)
RadioList htmlName overrides the internal useId group name selected item's value
Slider hidden input(s) String(value); two entries under one name in range mode
Selector hidden input selected value, "" when empty (like a placeholder <option value="">)
MultiSelector one hidden input per selected value native <select multiple> serialization (getAll)
Tokenizer one hidden input per selected item item ids

Disabled semantics are uniform: a disabled control never submits. Hidden carriers set disabled={isDisabled}; Switch/CheckboxInput withhold the name while disabled (with a disabledMessage the input intentionally stays focusable rather than natively disabled — see the existing aria-disabled pattern — so the name alone must gate submission); focusable-disabled radios detach with form="" since they must keep their name for grouping. The radio change also fixes a pre-existing leak: focusable-disabled RadioLists were submitting their selected value under the internal useId name (e.g. _r_0_=a) into any host form.

Everything is opt-in — without htmlName nothing changes (RadioList keeps its generated internal name).

Docs: htmlName rows added to all seven doc.mjs files in the English, docsZh, and docsDense exports (matching TextInput's three-export coverage), verified via astryx component <X> --zh/--dense.

Test plan

  • 19 new form participation tests across the seven suites, asserting through real new FormData(form): value inclusion, empty/unchecked exclusion, getAll ordering for multi-value, range-mode double entry, and disabled exclusion (including the focusable-disabled disabledMessage case for Switch/CheckboxInput/RadioList)
  • All seven component suites green: 283 tests
  • typecheck, typecheck:docs, eslint on all seven directories, sync:exports:check — clean
  • No React controlled-input warnings (verified: React exempts type="hidden")
  • Adversarially reviewed in three passes before opening; the disabled-submission inconsistency and a doc-placement issue were caught and fixed pre-push

Implements the hidden-native-input pattern from the facebook#3343 Tier 3 form
serialization strategy. Switch, CheckboxInput, RadioList, Slider,
Selector, MultiSelector, and Tokenizer accept the same htmlName prop
TextInput and NumberInput already had, so they serialize into native
form submission:

- Switch/CheckboxInput forward the name onto their native checkbox and
  withhold it while disabled (with a disabledMessage the input stays
  focusable rather than natively disabled, and a disabled control must
  not submit).
- RadioList exposes the group name (falling back to the internal
  useId name); focusable-disabled radios detach from the form with
  form="" so they keep their grouping name without submitting. This
  also stops the pre-existing leak of internal useId names into form
  data for focusable-disabled groups.
- Slider renders hidden inputs for the current value (two entries in
  range mode, like paired native range inputs).
- Selector renders one hidden input with the selected value (empty
  string when nothing is selected, like a placeholder option).
- MultiSelector/Tokenizer render one hidden input per selected
  value/item id, matching native multi-select serialization.
- All hidden carriers set disabled={isDisabled} so FormData excludes
  them exactly like native disabled controls.

Part of facebook#3343
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

@arham766 is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 4, 2026
@arham766 arham766 marked this pull request as ready for review July 4, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant