diff --git a/packages/react/src/otp-field/input/OTPFieldInput.tsx b/packages/react/src/otp-field/input/OTPFieldInput.tsx index 68c4259f8..d547e0cf4 100644 --- a/packages/react/src/otp-field/input/OTPFieldInput.tsx +++ b/packages/react/src/otp-field/input/OTPFieldInput.tsx @@ -101,8 +101,8 @@ export const OTPFieldInput = React.forwardRef(function OTPFieldInput( autoCorrect: 'off', spellCheck: 'false', enterKeyHint: index === length - 1 ? 'done' : 'next', - // Allow the first slot to accept a full code so browser paste/autofill can target it directly. - maxLength: index === 0 ? length : 1, + // Only the first slot has a max length to avoid password manager bubbles appearing after later inputs. + maxLength: index === 0 ? length : undefined, tabIndex: activeIndex === index ? 0 : -1, disabled, form, diff --git a/packages/react/src/otp-field/root/OTPFieldRoot.test.tsx b/packages/react/src/otp-field/root/OTPFieldRoot.test.tsx index f561cccfb..7929f71bc 100644 --- a/packages/react/src/otp-field/root/OTPFieldRoot.test.tsx +++ b/packages/react/src/otp-field/root/OTPFieldRoot.test.tsx @@ -71,6 +71,9 @@ describe('', () => { expect(inputs.map((input) => input.value)).toEqual(['1', '2', '3', '4', '5', '6']); expect(inputs[0]).toHaveAttribute('maxlength', '6'); + inputs.slice(1).forEach((input) => { + expect(input).not.toHaveAttribute('maxlength'); + }); expect(hiddenInput).toHaveValue('123456'); }); @@ -804,6 +807,16 @@ describe('', () => { expect(getValues()).toBe('123456'); }); + + it('replaces consecutive slots when typing multiple characters into a later input', async () => { + await render(); + + const inputs = screen.getAllByRole('textbox'); + + fireEvent.change(inputs[2], { target: { value: '99' } }); + + expect(getValues()).toBe('129956'); + }); }); describe('pasting', () => {