From 42321d7cb164eb924d60ea6d3dfeb35d50d766a1 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Fri, 28 Aug 2026 21:16:57 +0500 Subject: [PATCH 1/2] [Checkbox] Set the native indeterminate state on the input --- .../mui-material/src/Checkbox/Checkbox.js | 26 +++++++++++++------ .../src/Checkbox/Checkbox.test.js | 8 ++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/mui-material/src/Checkbox/Checkbox.js b/packages/mui-material/src/Checkbox/Checkbox.js index c822f1dcd54147..b2893d5edb9603 100644 --- a/packages/mui-material/src/Checkbox/Checkbox.js +++ b/packages/mui-material/src/Checkbox/Checkbox.js @@ -17,6 +17,8 @@ import memoTheme from '../utils/memoTheme'; import createSimplePaletteValueFilter from '../utils/createSimplePaletteValueFilter'; import { useDefaultProps } from '../DefaultPropsProvider'; import { mergeSlotProps } from '../utils'; +import useEnhancedEffect from '../utils/useEnhancedEffect'; +import useForkRef from '../utils/useForkRef'; import useSlot from '../utils/useSlot'; const useUtilityClasses = (ownerState) => { @@ -150,7 +152,17 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { const classes = useUtilityClasses(ownerState); - const externalInputProps = slotProps.input; + const externalInputProps = + typeof slotProps.input === 'function' ? slotProps.input(ownerState) : slotProps.input; + + const inputRef = React.useRef(null); + const handleInputRef = useForkRef(inputRef, externalInputProps?.ref); + + useEnhancedEffect(() => { + if (inputRef.current) { + inputRef.current.indeterminate = indeterminate; + } + }, [indeterminate]); const [RootSlot, rootSlotProps] = useSlot('root', { ref, @@ -176,15 +188,13 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { disableRipple: props.disableRipple, slots, slotProps: { - input: mergeSlotProps( - typeof externalInputProps === 'function' - ? externalInputProps(ownerState) - : externalInputProps, - { + input: { + ...mergeSlotProps(externalInputProps, { 'data-indeterminate': indeterminate, 'aria-checked': indeterminate ? 'mixed' : undefined, - }, - ), + }), + ref: handleInputRef, + }, }, }, }); diff --git a/packages/mui-material/src/Checkbox/Checkbox.test.js b/packages/mui-material/src/Checkbox/Checkbox.test.js index 5ff5111cb7e76e..e0255db4fb6aa9 100644 --- a/packages/mui-material/src/Checkbox/Checkbox.test.js +++ b/packages/mui-material/src/Checkbox/Checkbox.test.js @@ -116,6 +116,14 @@ describe('', () => { render(); expect(screen.getByRole('checkbox')).not.to.have.attribute('aria-checked'); }); + + it('should set the indeterminate property on the input', () => { + const { setProps } = render(); + expect(screen.getByRole('checkbox')).to.have.property('indeterminate', true); + + setProps({ indeterminate: false }); + expect(screen.getByRole('checkbox')).to.have.property('indeterminate', false); + }); }); describe('prop: size', () => { From e144af2d097e08b8fbbe377c7f6ac1363c7a8b36 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Fri, 28 Aug 2026 21:33:10 +0500 Subject: [PATCH 2/2] [Checkbox] Restore the native indeterminate state after activation --- docs/translations/api-docs/checkbox/checkbox.json | 2 +- packages/mui-material/src/Checkbox/Checkbox.d.ts | 5 ++--- packages/mui-material/src/Checkbox/Checkbox.js | 9 ++++++--- packages/mui-material/src/Checkbox/Checkbox.test.js | 13 ++++++++++++- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/translations/api-docs/checkbox/checkbox.json b/docs/translations/api-docs/checkbox/checkbox.json index 3ee9f6e0df8733..c93cbeef04fc36 100644 --- a/docs/translations/api-docs/checkbox/checkbox.json +++ b/docs/translations/api-docs/checkbox/checkbox.json @@ -15,7 +15,7 @@ "icon": { "description": "The icon to display when the component is unchecked." }, "id": { "description": "The id of the input element." }, "indeterminate": { - "description": "If true, the component appears indeterminate. This does not set the native input element to indeterminate due to inconsistent behavior across browsers. However, we set a data-indeterminate attribute on the input." + "description": "If true, the component appears indeterminate. This sets the native input element to indeterminate, and we also set a data-indeterminate attribute on the input." }, "indeterminateIcon": { "description": "The icon to display when the component is indeterminate." diff --git a/packages/mui-material/src/Checkbox/Checkbox.d.ts b/packages/mui-material/src/Checkbox/Checkbox.d.ts index 1a290fe66c6d99..f8473a7c7cb1cb 100644 --- a/packages/mui-material/src/Checkbox/Checkbox.d.ts +++ b/packages/mui-material/src/Checkbox/Checkbox.d.ts @@ -101,9 +101,8 @@ export interface CheckboxProps id?: SwitchBaseProps['id'] | undefined; /** * If `true`, the component appears indeterminate. - * This does not set the native input element to indeterminate due - * to inconsistent behavior across browsers. - * However, we set a `data-indeterminate` attribute on the `input`. + * This sets the native input element to indeterminate, + * and we also set a `data-indeterminate` attribute on the `input`. * @default false */ indeterminate?: boolean | undefined; diff --git a/packages/mui-material/src/Checkbox/Checkbox.js b/packages/mui-material/src/Checkbox/Checkbox.js index b2893d5edb9603..4425f63c55e590 100644 --- a/packages/mui-material/src/Checkbox/Checkbox.js +++ b/packages/mui-material/src/Checkbox/Checkbox.js @@ -192,6 +192,10 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { ...mergeSlotProps(externalInputProps, { 'data-indeterminate': indeterminate, 'aria-checked': indeterminate ? 'mixed' : undefined, + // Activating a checkbox clears its native indeterminate state, restore it. + onChange: (event) => { + event.target.indeterminate = indeterminate; + }, }), ref: handleInputRef, }, @@ -259,9 +263,8 @@ Checkbox.propTypes /* remove-proptypes */ = { id: PropTypes.string, /** * If `true`, the component appears indeterminate. - * This does not set the native input element to indeterminate due - * to inconsistent behavior across browsers. - * However, we set a `data-indeterminate` attribute on the `input`. + * This sets the native input element to indeterminate, + * and we also set a `data-indeterminate` attribute on the `input`. * @default false */ indeterminate: PropTypes.bool, diff --git a/packages/mui-material/src/Checkbox/Checkbox.test.js b/packages/mui-material/src/Checkbox/Checkbox.test.js index e0255db4fb6aa9..e4c223d7cc15b1 100644 --- a/packages/mui-material/src/Checkbox/Checkbox.test.js +++ b/packages/mui-material/src/Checkbox/Checkbox.test.js @@ -118,12 +118,23 @@ describe('', () => { }); it('should set the indeterminate property on the input', () => { - const { setProps } = render(); + render(); expect(screen.getByRole('checkbox')).to.have.property('indeterminate', true); + }); + + it('should unset the indeterminate property on the input when no longer indeterminate', () => { + const { setProps } = render(); setProps({ indeterminate: false }); expect(screen.getByRole('checkbox')).to.have.property('indeterminate', false); }); + + it('should keep the indeterminate property on the input after a click', async () => { + const { user } = render(); + + await user.click(screen.getByRole('checkbox')); + expect(screen.getByRole('checkbox')).to.have.property('indeterminate', true); + }); }); describe('prop: size', () => {