diff --git a/packages/react/src/checkbox/root/CheckboxRoot.test.tsx b/packages/react/src/checkbox/root/CheckboxRoot.test.tsx
index 936c88b22..9ec782577 100644
--- a/packages/react/src/checkbox/root/CheckboxRoot.test.tsx
+++ b/packages/react/src/checkbox/root/CheckboxRoot.test.tsx
@@ -1202,6 +1202,21 @@ describe('', () => {
expect(button).toHaveAttribute('aria-invalid', 'true');
});
+ it('validates once when changed by the user', async () => {
+ const validate = vi.fn();
+
+ const { user } = await render(
+
+
+ ,
+ );
+
+ await user.click(screen.getByRole('checkbox'));
+
+ expect(validate).toHaveBeenCalledTimes(1);
+ expect(validate.mock.lastCall?.[0]).toBe(true);
+ });
+
it('revalidates when a controlled value changes externally', async () => {
const validateSpy = vi.fn((value: unknown) => ((value as boolean) ? 'error' : null));
diff --git a/packages/react/src/checkbox/root/CheckboxRoot.tsx b/packages/react/src/checkbox/root/CheckboxRoot.tsx
index e1a566ee4..e19fa973c 100644
--- a/packages/react/src/checkbox/root/CheckboxRoot.tsx
+++ b/packages/react/src/checkbox/root/CheckboxRoot.tsx
@@ -259,10 +259,7 @@ export const CheckboxRoot = React.forwardRef(function CheckboxRoot(
? { value: (groupContext ? checked && valueProp : valueProp) || '' }
: EMPTY_OBJECT,
getDescriptionProps,
- (props) =>
- groupContext
- ? validation.getValidationProps(disabled, props)
- : validation.getInputValidationProps(disabled, props),
+ (props) => validation.getValidationProps(disabled, props),
);
const computedChecked = isGroupedWithParent ? Boolean(groupChecked) : checked;
diff --git a/packages/react/src/slider/root/SliderRoot.test.tsx b/packages/react/src/slider/root/SliderRoot.test.tsx
index 3dde9671b..147815718 100644
--- a/packages/react/src/slider/root/SliderRoot.test.tsx
+++ b/packages/react/src/slider/root/SliderRoot.test.tsx
@@ -2459,6 +2459,53 @@ describe.skipIf(typeof Touch === 'undefined')('', () => {
expect(input).toHaveAttribute('aria-invalid', 'true');
});
+ it('validates once when changed by the user', async () => {
+ const validate = vi.fn();
+
+ const { user } = await render(
+
+
+
+
+
+
+ ,
+ );
+
+ await user.keyboard('[Tab]');
+ expect(screen.getByRole('slider')).toHaveFocus();
+
+ await user.keyboard(`[${ARROW_RIGHT}]`);
+ await flushMicrotasks();
+
+ expect(validate).toHaveBeenCalledTimes(1);
+ expect(validate.mock.lastCall?.[0]).toBe(1);
+ });
+
+ it('validates once with an array value for range sliders when changed by the user', async () => {
+ const validate = vi.fn();
+
+ const { user } = await render(
+
+
+
+
+
+
+
+ ,
+ );
+
+ await user.keyboard('[Tab]');
+ expect(screen.getAllByRole('slider')[0]).toHaveFocus();
+
+ await user.keyboard(`[${ARROW_RIGHT}]`);
+ await flushMicrotasks();
+
+ expect(validate).toHaveBeenCalledTimes(1);
+ expect(validate.mock.lastCall?.[0]).toEqual([1, 5]);
+ });
+
it('revalidates when the controlled value changes externally', async () => {
const validateSpy = vi.fn((value: unknown) => (Number(value) === 5 ? 'error' : null));
diff --git a/packages/react/src/slider/thumb/SliderThumb.tsx b/packages/react/src/slider/thumb/SliderThumb.tsx
index 68768f100..157822c5e 100644
--- a/packages/react/src/slider/thumb/SliderThumb.tsx
+++ b/packages/react/src/slider/thumb/SliderThumb.tsx
@@ -452,7 +452,7 @@ export const SliderThumb = React.forwardRef(function SliderThumb(
type: 'range',
value: thumbValue ?? '',
},
- (props) => validation.getInputValidationProps(disabled, props),
+ (props) => validation.getValidationProps(disabled, props),
);
const mergedInputRef = useMergedRefs(inputRef, validation.inputRef, inputRefProp);
diff --git a/packages/react/src/switch/root/SwitchRoot.test.tsx b/packages/react/src/switch/root/SwitchRoot.test.tsx
index 6d8e5ca36..c9ac83bd1 100644
--- a/packages/react/src/switch/root/SwitchRoot.test.tsx
+++ b/packages/react/src/switch/root/SwitchRoot.test.tsx
@@ -831,6 +831,21 @@ describe('', () => {
expect(button).toHaveAttribute('aria-invalid', 'true');
});
+ it('validates once when changed by the user', async () => {
+ const validate = vi.fn();
+
+ const { user } = await render(
+
+
+ ,
+ );
+
+ await user.click(screen.getByRole('switch'));
+
+ expect(validate).toHaveBeenCalledTimes(1);
+ expect(validate.mock.lastCall?.[0]).toBe(true);
+ });
+
it('revalidates when a controlled value changes externally', async () => {
const validateSpy = vi.fn((value: unknown) => ((value as boolean) ? 'error' : null));
diff --git a/packages/react/src/switch/root/SwitchRoot.tsx b/packages/react/src/switch/root/SwitchRoot.tsx
index 29df41a62..b9b721e84 100644
--- a/packages/react/src/switch/root/SwitchRoot.tsx
+++ b/packages/react/src/switch/root/SwitchRoot.tsx
@@ -211,7 +211,7 @@ export const SwitchRoot = React.forwardRef(function SwitchRoot(
switchRef.current?.focus();
},
},
- (props) => validation.getInputValidationProps(disabled, props),
+ (props) => validation.getValidationProps(disabled, props),
// React <19 sets an empty value if `undefined` is passed explicitly
// To avoid this, we only set the value if it's defined
value !== undefined ? { value } : EMPTY_OBJECT,