Signal Formsa11y

Input OTP

Fixed-length one-time-code input. One cell per character, auto-advances focus on typing, handles paste of a full code, optional masking like a password field.

Installation

import { WrInputOtp } from 'ngwr/input-otp';

@Component({ imports: [WrInputOtp] })
export class MyComponent {
  protected code = '';
  protected verify(code: string) { /* … */ }
}

Basic (numeric, 6 cells)

The default. completed fires once every cell holds a character — paste the value 123456 to try it.

<wr-input-otp [(value)]="code" length="6" (completed)="verify($event)" />
<wr-input-otp [(value)]='code' (completed)='onCompleted($event)' />

Custom length

<wr-input-otp [(value)]='codeShort' length='4' />

Alphanumeric

Letters and digits. Useful for invitation codes.

<wr-input-otp [(value)]="alphaNumeric" mode="alphanumeric" length="8" />
<wr-input-otp [(value)]='alphaNumeric' mode='alphanumeric' length='8' />

Masked

Renders cells as type='password' — characters are hidden as you type.

<wr-input-otp [(value)]="secret" mask />
<wr-input-otp [(value)]='secret' mask />

Disabled

<wr-input-otp disabled />

API

NameDescriptionTypeDefault
ariaLabelAccessible name of the whole strip. Falls back to inputOtp.label.string | nullnull
lengthNumber of cells to render. Clamped to [1, 20].number6
modeCharacter set per cell.WrInputOtpMode'numeric'
sizeControl size — shares the --wr-control-* contract. Unset falls back to the inputOtp.size app default from provideWrConfig().WrInputOtpSize | null'md'
maskMask the typed characters like a password.booleanfalse
disabledDisable interaction. Bound automatically from the field's disabled state when used with [formField].booleanfalse
readonlyRefuse edits while every box stays focusable and the code still submits. Bound automatically from the field's readonly state when used with [formField]. Each box is a real text input, so this is the native readonly attribute — arrow keys, Home / End and selection keep working, which is the whole difference from disabled. Paste is cancelled too, since a paste still reaches a read-only input even though typing does not.booleanfalse
placeholderCharacter shown in empty cells.string'•'
valueThe entered code. Bound by [formField], or two-way via [(value)].string''
(completed)Fires once when every cell holds a character.string
(touch)Emitted on blur so a bound field can mark itself touched.void

Events

NameDescriptionTypeDefault
(completed)Fires once every cell holds a character — useful for auto-submit.string

CSS variables

Custom properties ngwr/input-otp publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.

VariableDefaultDeclared on
--wr-input-otp-bgvar(--wr-color-surface).wr-input-otp
--wr-input-otp-bordervar(--wr-color-outline).wr-input-otp
--wr-input-otp-colorvar(--wr-color-on-surface).wr-input-otp
--wr-input-otp-focusvar(--wr-color-primary).wr-input-otp
--wr-input-otp-fontvar(--wr-text-lg).wr-input-otp +2 variant overrides
--wr-input-otp-gap0.5rem.wr-input-otp +2 variant overrides
--wr-input-otp-radiusvar(--wr-control-radius-md).wr-input-otp +2 variant overrides
--wr-input-otp-size2.5rem.wr-input-otp +2 variant overrides