diff --git a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx
index 08c8603dc16c5a..53946ece059bdb 100644
--- a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx
+++ b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx
@@ -270,4 +270,42 @@ describe('useNumberInput', () => {
expect(handleChange.args[0][1]).to.equal(undefined);
});
});
+
+ describe('warnings', () => {
+ it('should warn when switching from uncontrolled to controlled', () => {
+ const handleChange = spy();
+ function NumberInput({ value }: { value?: number }) {
+ const { getInputProps } = useNumberInput({
+ onChange: handleChange,
+ value,
+ });
+
+ return ;
+ }
+ const { setProps } = render();
+ expect(() => {
+ setProps({ value: 5 });
+ }).to.toErrorDev(
+ 'MUI: A component is changing the uncontrolled value state of NumberInput to be controlled',
+ );
+ });
+
+ it('should warn when switching from controlled to uncontrolled', () => {
+ const handleChange = spy();
+ function NumberInput({ value }: { value?: number }) {
+ const { getInputProps } = useNumberInput({
+ onChange: handleChange,
+ value,
+ });
+
+ return ;
+ }
+ const { setProps } = render();
+ expect(() => {
+ setProps({ value: undefined });
+ }).to.toErrorDev(
+ 'MUI: A component is changing the controlled value state of NumberInput to be uncontrolled',
+ );
+ });
+ });
});
diff --git a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts
index dd2646f83025e2..8314a22113433f 100644
--- a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts
+++ b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts
@@ -1,7 +1,11 @@
'use client';
import * as React from 'react';
import MuiError from '@mui/utils/macros/MuiError.macro';
-import { unstable_useForkRef as useForkRef, unstable_useId as useId } from '@mui/utils';
+import {
+ unstable_useForkRef as useForkRef,
+ unstable_useId as useId,
+ unstable_useControlled as useControlled,
+} from '@mui/utils';
import { FormControlState, useFormControlContext } from '../FormControl';
import {
UseNumberInputParameters,
@@ -81,7 +85,12 @@ export function useNumberInput(parameters: UseNumberInputParameters): UseNumberI
const [focused, setFocused] = React.useState(false);
// the "final" value
- const [value, setValue] = React.useState(valueProp ?? defaultValueProp);
+ const [value, setValue] = useControlled({
+ controlled: valueProp,
+ default: defaultValueProp,
+ name: 'NumberInput',
+ });
+
// the (potentially) dirty or invalid input value
const [dirtyValue, setDirtyValue] = React.useState(
value ? String(value) : undefined,