Skip to content

fix(OTPInput): accept a code delivered by SMS autofill or a password manager - #668

Merged
mrholek merged 2 commits into
mainfrom
fix/otp-autofill
Aug 2, 2026
Merged

fix(OTPInput): accept a code delivered by SMS autofill or a password manager#668
mrholek merged 2 commits into
mainfrom
fix/otp-autofill

Conversation

@mrholek

@mrholek mrholek commented Aug 2, 2026

Copy link
Copy Markdown
Member

Backport of the v6 fix (#665) to the v5 line, verified against the v5 sources rather than assumed.

The bug

Autofill and password managers insert the whole code at once and fire input, never paste — and the input handler only accepted value.length === 1, while every slot carried maxlength=1 and autocomplete="one-time-code". The code therefore stayed in the slot that received it (or was truncated to a single character by the browser), the hidden form control kept its old value, and neither change nor complete fired — so the form submitted an empty OTP and auto-submit flows never ran.

The fix

  • A multi-character value arriving in any slot is spread across the field through the same path as a paste (_distributeChars); a value at least as long as the field is treated as a complete code and fills from the first slot, whichever slot is focused.
  • The hidden value is read back from the slots, so a partial paste no longer discards characters already entered (9 + paste 87987, was 87).
  • Only the first slot advertises autocomplete="one-time-code"; the rest are off, so autofill targets a single field. The first slot accepts the whole code while empty and shrinks back to one character once filled; clear() and reset() restore that capacity.
  • Slots gained enterkeyhint, autocapitalize="off", autocorrect="off" and spellcheck="false".

The attribute recipe follows Base UI's OTP field, which uses the same one-input-per-slot architecture we do.

Tests

Eight regression tests, all red before the fix, plus the amended attribute test. Full karma suite green (3017).

Bundle budgets

The fix adds ~0.6 kB gzip to the unminified JS bundles and ~0.15 kB to the minified ones; the previous budgets left 0.06–0.26 kB of headroom, so .bundlewatch.config.json is raised in a separate commit.

Parity

The same fix ships for React (coreui-react-pro), Vue (coreui-vue-pro) and Angular (coreui-angular-pro) on the same branch name.

mrholek added 2 commits August 2, 2026 19:44
…manager

Autofill and password managers insert the whole code at once and fire `input`,
never `paste` — and the `input` handler only accepted `value.length === 1`,
while every slot carried `maxlength=1` and `autocomplete="one-time-code"`.
The code therefore stayed in the slot that received it (or was truncated to a
single character), the hidden form control kept its old value, and neither
`change` nor `complete` fired — so the form submitted an empty OTP and
auto-submit flows never ran.

- A multi-character value arriving in any slot is now spread across the field
  through the same path as a paste; a value at least as long as the field is
  treated as a complete code and fills from the first slot.
- The hidden value is read back from the slots, so a partial paste no longer
  discards characters already entered (`9` + paste `87` => `987`, was `87`).
- Only the first slot advertises `autocomplete="one-time-code"`; the rest are
  `off`, so autofill targets a single field. The first slot accepts the whole
  code while empty and shrinks back to one character once filled, and `clear()`
  / `reset()` restore that capacity.
- Slots gained `enterkeyhint`, `autocorrect="off"` and `spellcheck="false"`.

Backport of the v6 fix (#665); the attribute recipe follows Base UI's OTP
field, which uses the same one-input-per-slot architecture we do. Eight
regression tests, all red before the fix.
The fix adds ~0.6 kB gzip to the unminified bundles and ~0.15 kB to the
minified ones, and the previous budgets left 0.06-0.26 kB of headroom.
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 30760316824

Coverage increased (+0.03%) to 94.677%

Details

  • Coverage increased (+0.03%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (30 of 31 lines covered, 96.77%).
  • 3 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
js/src/otp-input.js 31 30 96.77%

Coverage Regressions

3 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
js/src/range-slider.js 3 94.71%

Coverage Stats

Coverage Status
Relevant Lines: 7552
Covered Lines: 7273
Line Coverage: 96.31%
Relevant Branches: 3137
Covered Branches: 2847
Branch Coverage: 90.76%
Branches in Coverage %: Yes
Coverage Strength: 726.52 hits per line

💛 - Coveralls

@mrholek
mrholek merged commit 651e627 into main Aug 2, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants