Skip to content

[all components] Type render callback props to the rendered element - #5104

Merged
atomiks merged 6 commits into
mui:masterfrom
atomiks:claude/hardcore-elbakyan-30ba96
Jul 1, 2026
Merged

[all components] Type render callback props to the rendered element#5104
atomiks merged 6 commits into
mui:masterfrom
atomiks:claude/hardcore-elbakyan-30ba96

Conversation

@atomiks

@atomiks atomiks commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #5102

The third generic of BaseUIComponentProps types the render callback's props. It defaults to HTMLProps so the callback can spread onto any element. Specializing it to ComponentPropsWithRef<'tag'> exposes element-specific props (src, href, disabled, …) but blocks rendering as a different element. So we specialize only parts locked to one element, and keep HTMLProps for parts that can legitimately render as something else.

Specialized

Part Element
Avatar.Image <img>
Form <form>
NumberField.Input <input>
OTPField.Input <input>
PreviewCard.Trigger <a>
Menu.LinkItem <a>
NavigationMenu.Link <a>
Toolbar.Link <a>

Kept loose (HTMLProps)

Part Reason
Combobox.Input / Autocomplete.Input can render as <textarea>
Field.Control, Toolbar.Input generic input, can be <textarea>
Field.Label nativeLabel={false} supports <div>
Fieldset.Root can render group controls such as RadioGroup, CheckboxGroup, or Slider.Root
Slider.Value often a <span>/<div>; shouldn't force htmlFor
NavigationMenu.List <ul> has no element-specific props
*.Button parts polymorphic via nativeButton

Also

  • Combobox.Input / Autocomplete.Input: dropped the injected type="text" (native default already; was invalid when rendered as a <textarea>).
  • Added type-level *.spec.tsx checks for the specialized callbacks.

Specialize the render callback's props (the third generic of BaseUIComponentProps) to ComponentPropsWithRef<'tag'> for parts locked to a single element: Avatar.Image, Form, Fieldset.Root, NumberField.Input, OTPField.Input, and the anchor links (PreviewCard.Trigger, Menu.LinkItem, NavigationMenu.Link, Toolbar.Link). Keep the loose HTMLProps default for polymorphic parts (Field.Control, Toolbar.Input, Combobox/Autocomplete Input, Field.Label, Slider.Value, *.Button parts) so they can still be rendered as a different element.

Also drop the injected type='text' from Combobox/Autocomplete Input (native default already; was invalid when rendered as a <textarea>).
@atomiks atomiks added typescript type: bug It doesn't behave as expected. labels Jun 23, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 23, 2026

Copy link
Copy Markdown

commit: 0bef2d0

@code-infra-dashboard

code-infra-dashboard Bot commented Jun 23, 2026

Copy link
Copy Markdown

Bundle size

Bundle Parsed size Gzip size
@base-ui/react ▼-12B(0.00%) ▼-4B(0.00%)

Details of bundle changes

Performance

Total duration: 1,297.28 ms -96.59 ms(-6.9%) | Renders: 78 (+0)

No significant changes — details


Check out the code infra dashboard for more information about this PR.

@netlify

netlify Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 0bef2d0
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/6a44a3048eb8460008f0614d
😎 Deploy Preview https://deploy-preview-5104--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

atomiks added 2 commits June 23, 2026 17:35
The render prop type column now resolves to the element-specific props for the parts whose render callback was specialized (Avatar.Image, Form, Fieldset.Root, NumberField.Input, OTPField.Input, and the anchor links incl. Menu/ContextMenu LinkItem).
Pins two things for rendering Combobox/Autocomplete Input as a <textarea> via the render prop: it no longer receives an invalid `type` attribute (guards the removed injected type='text'), and the combobox ARIA attributes (role, aria-expanded, aria-controls) are applied only while the popup is open — a non-input control omits them when closed, unlike a native <input> which always exposes them.
@atomiks
atomiks force-pushed the claude/hardcore-elbakyan-30ba96 branch from 7e508eb to 50ceaee Compare June 23, 2026 07:59
@atomiks atomiks added the scope: all components Widespread work has an impact on almost all components. label Jun 23, 2026
@atomiks
atomiks marked this pull request as ready for review June 23, 2026 08:14
@atomiks
atomiks requested a review from Copilot June 29, 2026 04:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR improves Base UI’s type safety for render callback props by specializing the third generic of BaseUIComponentProps to the actual rendered intrinsic element for selected components (e.g. <a>, <img>, <input>, <form>, <fieldset>), while keeping looser typing where polymorphism is intended. It also removes an injected type="text" from Combobox.Input to support rendering as <textarea> without invalid attributes, and updates type-level checks and generated docs accordingly.

Changes:

  • Specialized BaseUIComponentProps<..., ..., RenderFunctionProps> for several parts so render={(props) => ...} exposes the correct intrinsic props (e.g. href, src, disabled).
  • Removed injected type: 'text' from Combobox.Input and added runtime tests ensuring <textarea> rendering stays valid and editable.
  • Added/expanded type-level *.spec.tsx checks and updated generated docs to reflect the new callback prop types.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/react/src/toolbar/link/ToolbarLink.tsx Specializes render callback props to native <a> props.
packages/react/src/toolbar/link/ToolbarLink.spec.tsx Adds type-level check that render props include href.
packages/react/src/preview-card/trigger/PreviewCardTrigger.tsx Specializes render callback props to native <a> props.
packages/react/src/preview-card/trigger/PreviewCardTrigger.spec.tsx Adds type-level check that render props include href.
packages/react/src/otp-field/input/OTPFieldInput.tsx Specializes render callback props to native <input> props.
packages/react/src/otp-field/input/OTPFieldInput.spec.tsx Adds type-level checks for <input>-specific props in render.
packages/react/src/number-field/input/NumberFieldInput.tsx Specializes render callback props to native <input> props.
packages/react/src/number-field/input/NumberFieldInput.spec.tsx Adds type-level checks for <input>-specific props in render.
packages/react/src/navigation-menu/link/NavigationMenuLink.tsx Specializes render callback props to native <a> props.
packages/react/src/navigation-menu/link/NavigationMenuLink.spec.tsx Adds type-level check that render props include href.
packages/react/src/menu/link-item/MenuLinkItem.tsx Specializes render callback props to native <a> props.
packages/react/src/menu/link-item/MenuLinkItem.spec.tsx Adds type-level check that render props include href.
packages/react/src/form/Form.tsx Specializes render callback props to native <form> props.
packages/react/src/form/Form.spec.tsx Adds type-level check that render props include noValidate.
packages/react/src/fieldset/root/FieldsetRoot.tsx Specializes render callback props to native <fieldset> props.
packages/react/src/fieldset/root/FieldsetRoot.spec.tsx Adds type-level check that render props include disabled.
packages/react/src/combobox/input/ComboboxInput.tsx Removes injected type="text" to allow <textarea> rendering without invalid attributes.
packages/react/src/combobox/input/ComboboxInput.test.tsx Adds runtime coverage for rendering Combobox.Input as <textarea>.
packages/react/src/avatar/image/AvatarImage.tsx Specializes render callback props to native <img> props.
packages/react/src/avatar/Avatar.spec.tsx Adds type-level checks that Avatar.Image render props include src/alt.
docs/src/app/(docs)/react/components/toolbar/types.md Updates generated API docs for specialized <a> render prop typing.
docs/src/app/(docs)/react/components/preview-card/types.md Updates generated API docs for specialized <a> render prop typing.
docs/src/app/(docs)/react/components/otp-field/types.md Updates generated API docs for specialized <input> render prop typing.
docs/src/app/(docs)/react/components/number-field/types.md Updates generated API docs for specialized <input> render prop typing.
docs/src/app/(docs)/react/components/navigation-menu/types.md Updates generated API docs for specialized <a> render prop typing.
docs/src/app/(docs)/react/components/menu/types.md Updates generated API docs for specialized <a> render prop typing.
docs/src/app/(docs)/react/components/form/types.md Updates generated API docs for specialized <form> render prop typing.
docs/src/app/(docs)/react/components/fieldset/types.md Updates generated API docs for specialized <fieldset> render prop typing.
docs/src/app/(docs)/react/components/context-menu/types.md Updates generated API docs impacted by specialized <a> render prop typing.
docs/src/app/(docs)/react/components/avatar/types.md Updates generated API docs for specialized <img> render prop typing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@flaviendelangle flaviendelangle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

PR review

This PR is type-only except for one safe runtime change (dropping the injected type="text" on Combobox.Input/Autocomplete.Input). The native-input-vs-other-element ARIA branch keys off tagName === 'INPUT' (AriaCombobox.tsx:992), not the type attribute, so the removal doesn't disturb it, and the new tests pin both the editability and the open-only ARIA behavior. The render-callback specialization only propagates each component's already-declared element type ('img', 'form', 'input', 'a') to the callback param, so it can't tighten anything beyond what the component's own props already required. Nothing is merge-blocking.

Bugs (0)

No findings.

Tests (0)

No findings.

Simplifications (0)

No findings.

Docs (2)

1. ℹ️ Auto-generated render type is now noisy in public API tables

Location: docs/src/app/(docs)/react/components/form/types.md:11 (and the avatar/number-field/otp-field/preview-card/menu/navigation-menu/toolbar tables)

| render | `ReactElement \| ((props: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, state: Form.State) => ReactElement)` | ... |

The specialized third generic causes pnpm docs:api to emit the fully-expanded React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> in place of the prior clean HTMLProps. It's accurate but considerably harder to read in the published prop table.

Failure scenario: A user scanning the Form/Avatar/Input prop tables sees a long leaky type for render instead of a concise one; purely a readability regression in the generated docs.

Fix: Optional — if the docs generator supports a type alias/display override, map these back to a short name; otherwise accept it as the cost of the more precise type.

2. ℹ️ Combobox.Input no longer emits a type="text" attribute

Location: packages/react/src/combobox/input/ComboboxInput.tsx:205

// removed:
type: 'text',

Behaviorally inert for a native <input> (text is the default), and the PR documents it as intentional. The only observable effect is that the rendered DOM element no longer carries type="text", so an app styling the input via an input[type="text"] selector would stop matching. The in-repo demos that rely on type="text" pass it explicitly (autocomplete/demos/grid/...), so they're unaffected.

Failure scenario: A consumer with CSS like .MyCombobox input[type="text"] { ... } loses those styles after upgrading.

Fix: None needed; consider a one-line changelog/migration note since it's a visible DOM-attribute change.

Verdict

Approve — type-only specialization plus a safe, well-tested type="text" removal; no correctness or test gaps, only minor cosmetic notes on generated docs.


🤖 Review generated with Claude Code

@atomiks
atomiks merged commit 6c3195e into mui:master Jul 1, 2026
23 checks passed
@atomiks
atomiks deleted the claude/hardcore-elbakyan-30ba96 branch July 1, 2026 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: all components Widespread work has an impact on almost all components. type: bug It doesn't behave as expected. typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avatar.Image's render prop has the wrong prop types

3 participants