From 1cded850d1998d3b6d0ac7d9c860c43d4af55f5b Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Thu, 12 Mar 2026 22:49:26 +0800 Subject: [PATCH 1/6] Use non-native label for TextField.select --- packages/mui-material/src/Input/Input.js | 3 +- .../mui-material/src/Select/SelectInput.js | 2 + .../mui-material/src/TextField/TextField.js | 23 +++++--- .../src/TextField/TextField.test.js | 59 ++++++++++++++++++- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/packages/mui-material/src/Input/Input.js b/packages/mui-material/src/Input/Input.js index 3839679f3c1321..6091df22b6b1cb 100644 --- a/packages/mui-material/src/Input/Input.js +++ b/packages/mui-material/src/Input/Input.js @@ -10,6 +10,7 @@ import { styled } from '../zero-styled'; import memoTheme from '../utils/memoTheme'; import createSimplePaletteValueFilter from '../utils/createSimplePaletteValueFilter'; import { useDefaultProps } from '../DefaultPropsProvider'; +import inputLabelClasses from '../InputLabel/inputLabelClasses'; import inputClasses, { getInputUtilityClass } from './inputClasses'; import { rootOverridesResolver as inputBaseRootOverridesResolver, @@ -62,7 +63,7 @@ const InputRoot = styled(InputBaseRoot, { { props: ({ ownerState }) => ownerState.formControl, style: { - 'label + &': { + [`.${inputLabelClasses.root} + &`]: { marginTop: 16, }, }, diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js index f3265fba214764..c0128c13a904ec 100644 --- a/packages/mui-material/src/Select/SelectInput.js +++ b/packages/mui-material/src/Select/SelectInput.js @@ -524,6 +524,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { }; const listboxId = useId(); + const nativeInputId = useId(); return ( @@ -574,6 +575,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { autoFocus={autoFocus} required={required} {...other} + id={other.id ?? nativeInputId} ownerState={ownerState} /> diff --git a/packages/mui-material/src/TextField/TextField.js b/packages/mui-material/src/TextField/TextField.js index 6992ae1f9cb042..889d824b24dc5e 100644 --- a/packages/mui-material/src/TextField/TextField.js +++ b/packages/mui-material/src/TextField/TextField.js @@ -140,6 +140,14 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { slotProps, }; + const [SelectSlot, selectProps] = useSlot('select', { + elementType: Select, + externalForwardedProps, + ownerState, + }); + + const nativeSelect = select && selectProps.native; + const inputAdditionalProps = {}; const inputLabelSlotProps = externalForwardedProps.slotProps.inputLabel; @@ -151,7 +159,7 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { } if (select) { // unset defaults from textbox inputs - if (!slotProps.select || !slotProps.select.native) { + if (!nativeSelect) { inputAdditionalProps.id = undefined; } inputAdditionalProps['aria-describedby'] = undefined; @@ -202,12 +210,6 @@ const TextField = React.forwardRef(function TextField(inProps, ref) { ownerState, }); - const [SelectSlot, selectProps] = useSlot('select', { - elementType: Select, - externalForwardedProps, - ownerState, - }); - const InputElement = ( {label != null && label !== '' && ( - + {label} )} diff --git a/packages/mui-material/src/TextField/TextField.test.js b/packages/mui-material/src/TextField/TextField.test.js index 4d65cd07b1c95b..89eee88180ec69 100644 --- a/packages/mui-material/src/TextField/TextField.test.js +++ b/packages/mui-material/src/TextField/TextField.test.js @@ -283,7 +283,62 @@ describe('', () => { expect(screen.getByRole('combobox')).toHaveAccessibleName('Release:'); }); - it('creates an input[hidden] that has no accessible properties', () => { + it('renders the label as a
without htmlFor when select', () => { + render( + + Stable + , + ); + + const labelElement = screen.getByText('Release'); + expect(labelElement.tagName).to.equal('DIV'); + expect(labelElement).not.to.have.attribute('for'); + + const combobox = screen.getByRole('combobox'); + expect(combobox).to.have.attribute('aria-labelledby'); + expect(combobox.getAttribute('aria-labelledby')).to.include(labelElement.id); + }); + + it('renders the label as a
when native is set via slotProps', () => { + render( + + Stable + , + ); + + const labelElement = screen.getByText('Release'); + expect(labelElement.tagName).to.equal('DIV'); + expect(labelElement).not.to.have.attribute('for'); + + const combobox = screen.getByRole('combobox'); + expect(combobox.getAttribute('aria-labelledby')).to.include(labelElement.id); + }); + + it('renders the label as a