Skip to content

OTP / PIN input

In review
Live · web

Live: type, paste, or tab in (try 424242)

Verification code

Enter the 6-digit code we sent you.

States: partial, error, success

Partially filled code

Keep going, 3 digits left.

Wrong code
Verified code

Verified.

Lengths & variants: 4-cell, grouped 3·3, masked, disabled

Four-digit PIN

A shorter 4-cell code.

Grouped code

A separator splits the code 3·3 for readability.

Masked passcode

Each filled cell shows a dot, not the digit.

Disabled code

Inert, but still readable.

Live code: ______ · verify attempts 0 · awaiting input. The island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,
// not yet exported from @dizyx/nockerl-react; promotion is tracked.)
import { useState } from 'react';
export function VerifyStep({ onVerified }: { onVerified: (code: string) => void }) {
const [code, setCode] = useState('');
const [status, setStatus] = useState<'idle' | 'validating' | 'error' | 'success'>('idle');
return (
<OtpInput
groupLabel="Verification code"
length={6}
value={code}
onValueChange={setCode}
onComplete={async (entered) => {
setStatus('validating');
const ok = await verifyCode(entered); // server-side check
setStatus(ok ? 'success' : 'error');
if (ok) onVerified(entered);
}}
status={status}
message={status === 'error' ? 'That code is incorrect.' : 'Enter the 6-digit code we sent you.'}
/>
);
}
PropTypeDefaultDescription
value *stringThe code so far (controlled). Shorter than length = partially filled.
groupLabel *stringAccessible name for the whole row. Each cell derives digit N of M from it.
onValueChange(value: string) => voidFires on every keystroke / paste with the full string so far.
onComplete(value: string) => voidFires once the final cell is filled, the hook for server-side verification.
lengthnumber6Number of single-character cells (e.g. 4 or 6).
status'idle' | 'validating' | 'error' | 'success''idle'Drives the cell border, the trailing glyph, and the message tone. error shakes (reduced-motion freezes it).
messagestringThe single line under the row (helper / error / success). Wired via aria-describedby; live region.
groupAfternumberInsert a centered separator after this many cells (e.g. 3 → a 3·3 grouped code).
maskbooleanfalseRender each filled cell as a dot (a password-style passcode).
numericbooleantrueRestrict to digits: sets inputMode="numeric" and strips non-digits.
disabledbooleanfalseInert and clearly seen (never faded to invisible).