Skip to content

feat(forms): render any native form control under rx.form.control - #7859

Merged
FarhanAliRaza merged 8 commits into
mainfrom
claude/form-control-children
Nov 12, 2026
Merged

FarhanAliRaza merged 8 commits into
mainfrom
claude/form-control-children

Conversation

@masenf

@masenf masenf commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Description

Stacked on #7724, which teaches form compilation that a control rendered by rx.form.control takes its rx.form.field's name.

rx.form.control only accepted rx.input and rx.debounce_input as its child, so a textarea or a low-level input, textarea or select could not take a form field's name and validation messages:

TypeError: Only Radix TextFieldRoot and DebounceInput are allowed as children of FormControl

Upstream Radix Form.Control composes with any native form control, and with nothing else: the Radix docs state that it cannot yet be composed with the other form primitives such as Checkbox and Select.

  • FormControl.create now accepts every component backed by a native input, select or textarea, which covers rx.input, rx.text_area, rx.debounce_input, rx.el.input, rx.el.textarea and rx.el.select, and keeps rejecting the rest with a message that says to give those controls a name and place them under the root or field instead.
  • A child is only rendered as the control with asChild, so as_child is set whenever a child is given, even over an explicit as_child=False; without it Radix renders an <input> with children, which React refuses and the page fails to render.
  • The low-level form docs list what a Form Control renders and where the unsupported controls go.

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Unit tests cover acceptance of each native control and the exact rejection message. The new Playwright test tests/integration/tests_playwright/test_radix_form_control.py runs a form with one field per accepted control, including a fully controlled input that compiles to DebounceInput, and checks that each control carries its field's name and the id its label points to, that a required textarea blocks submission and shows its rx.form.message, and that the submitted payload holds every value, with no page errors.


Generated by Claude Code

View guided diff Turn on auto-fix

Radix passes an rx.form.field's name to the control rendered by its
rx.form.control, so an rx.input there submits under the field's name even
without a name of its own. The static form walk only read each control's
own name, so compiling such a form warned that the control is submitted
by its id, and a TypedDict submit handler raised a form field mismatch
for the field the control fills.

Walk form controls with the name their enclosing form field control
passes on: its own name, or else the enclosing form field's. A form
control without a child renders an input and is the control itself; with
a child it names the child. Components opt into these roles with the new
_is_form_field and _is_form_field_control class flags, set on
rx.form.field and rx.form.control.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESN8DjjKz1VPEvFQd5BuvU
rx.form.control did not declare name, so a name passed to it landed in
its style as CSS while the browser still submitted the control under the
enclosing field's name. The form walk read that undeclared attribute and
took it as the control's name, so a TypedDict submit handler could fail
for a form the browser submits correctly.

Declare name on FormControl so Radix receives it and prefers it over the
field's name, which is what the walk already assumes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESN8DjjKz1VPEvFQd5BuvU
Compare the deprecation warning, the rendered control props and the
TypedDict mismatch error in full, as the testing guidelines now ask,
instead of checking fragments of them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESN8DjjKz1VPEvFQd5BuvU
@greptile-apps

greptile-apps Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The reviewed changes appear safe to merge.

Summary

rx.form.control now accepts native inputs, selects, and textareas, including the supported Reflex wrappers.

  • Form controls can use native inputs, text areas, and selects as children.

No new actionable issues were found in the changes since the previous review. Tests were inspected, not run. No previous review threads were supplied.

Reviews (5) · Last reviewed commit: "fix(forms): render a form control's chil..." · Reviewed by Greptile

…hild only

Drop the _is_form_field and _is_form_field_control class variables from
Component: rx.form.field and rx.form.control set the attributes and the
form walk reads them with getattr, so nothing else carries them.

Pass the name a form field control supplies to the child it renders
only. A hidden input nested in that child, such as one in an
rx.input.slot, is not named by Radix, so it keeps its warning that
submitting it by id is deprecated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESN8DjjKz1VPEvFQd5BuvU
rx.form.control only accepted rx.input and rx.debounce_input as its
child, so a textarea or a low-level input, textarea or select could not
take a form field's name and validation messages. Radix Form.Control
composes with any native form control, and with nothing else: its other
primitives such as Checkbox and Select do not work under it.

Accept every component backed by a native input, select or textarea,
which covers rx.text_area, rx.el.input, rx.el.textarea and rx.el.select,
and keep rejecting the rest with a message that says where those go.
A child is only rendered as the control with asChild, so set it when a
child is given.

The Playwright test submits a form with one field per accepted control
and checks that each carries its field's name and label id, that the
required textarea blocks submission and shows its message, and that the
payload holds every value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESN8DjjKz1VPEvFQd5BuvU
@codspeed

codspeed Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 5.69%

2 warnings

⚠️ 17 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ 1 benchmark with a significant change ran on different hardware

Its base and head results were measured on machines with a different CPU, core count or memory, so the change might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

⚡ 1 improved benchmark
✅ 150 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_route_matching[1000] 50.7 µs 47.9 µs +5.69%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/form-control-children (16c8209) with main (8f1856f)

Open in CodSpeed

@masenf
masenf marked this pull request as ready for review November 12, 2026 00:14
@masenf
masenf requested review from a team and Alek99 as code owners November 12, 2026 00:14
@masenf masenf added the on deck PRs lined up to review / merge next label Nov 12, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/integration/tests_playwright/test_radix_form_control.py">

<violation number="1" location="tests/integration/tests_playwright/test_radix_form_control.py:17">
P2: This test does not cover `rx.debounce_input`, which `rx.form.control` accepts. Add a `debounce_input` field, for example `form_field("debounced", rx.debounce_input(rx.el.input(default_value="debounced input")))`, and add it to `FIELD_NAMES` and the expected submitted dict. Otherwise the docstring's claim about every accepted control is not backed by the test.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

@FarhanAliRaza FarhanAliRaza added the enhancement Anything you want improved label Nov 12, 2026 — with ChatGPT Codex Connector
Base automatically changed from claude/clever-ride-aljh3h to main November 12, 2026 16:06
# Conflicts:
#	docs/library/forms/form-ll.md
#	pyi_hashes.json
#	tests/units/components/forms/test_form.py
…hild=False

An explicit as_child=False with a child made Radix render an <input> with
children, which React refuses, so the page failed to render. The Playwright
test now relies on the default instead of passing as_child=True.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py">

<violation number="1" location="packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py:20">
P2: `FormControl.create` rejects custom components that directly render a native `input`, `select`, or `textarea` because it checks only concrete class inheritance. Accept native-tag children in addition to the known wrappers so the advertised “component rendering” contract covers custom native controls.</violation>

<violation number="2" location="packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py:20">
P2: `DebounceInput` is accepted without checking the control it wraps. `rx.debounce_input(rx.checkbox(on_change=...))` passes this check, even though `rx.checkbox` is a Radix control the rejection message says must be refused. Check the wrapped element's type, or reject `DebounceInput` children whose `element` is not a native control.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

@FarhanAliRaza
FarhanAliRaza merged commit e0114b3 into main Nov 12, 2026
168 checks passed
@FarhanAliRaza
FarhanAliRaza deleted the claude/form-control-children branch November 12, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Anything you want improved on deck PRs lined up to review / merge next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants