OTP / PIN input
Live: type, paste, or tab in (try 424242)
Enter the 6-digit code we sent you.
States: partial, error, success
Keep going, 3 digits left.
That code is incorrect.
Verified.
Lengths & variants: 4-cell, grouped 3·3, masked, disabled
A shorter 4-cell code.
A separator splits the code 3·3 for readability.
Each filled cell shows a dot, not the digit.
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.'} /> );}// Android: Jetpack Compose. NO formal OTP component ships yet; this is the real// building-block idiom (the LoginScreen auto-advance pattern), pending a packaged// NockerlOtpInput. Each cell is an OutlinedTextField(maxLength 1) at the shared// NockerlControlShape; KeyboardOptions(imeAction = Next) + focusManager.moveFocus// drive auto-advance, exactly as the email -> password step does today.val focusManager = LocalFocusManager.currentvar code by remember { mutableStateOf("") }
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { repeat(6) { i -> OutlinedTextField( value = code.getOrNull(i)?.toString() ?: "", onValueChange = { raw -> val digit = raw.filter(Char::isDigit).take(1) code = code.toMutableList().also { while (it.size <= i) it.add(' '); it[i] = digit.firstOrNull() ?: ' ' }.joinToString("").trim() if (digit.isNotEmpty()) focusManager.moveFocus(FocusDirection.Next) // auto-advance }, singleLine = true, shape = NockerlControlShape, // the shared 12dp control radius keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.NumberPassword, imeAction = if (i == 5) ImeAction.Done else ImeAction.Next, ), colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = MaterialTheme.colorScheme.primary, errorBorderColor = MaterialTheme.colorScheme.error, ), modifier = Modifier.width(48.dp), ) }}// macOS SwiftUI. NOT IMPLEMENTED in Nockerl Voice: no verification-code or// device-pairing UI ships today. This is the intended NockerlOtpInput API,// designed from the field + validation vocabulary; treat it as a proposal until// the Voice package ships it. (See the drift note below.)NockerlOtpInput( code: $code, length: 6, groupLabel: "Verification code", status: status, // .idle / .validating / .error / .success message: status == .error ? "That code is incorrect." : "Enter the 6-digit code we sent you.", onComplete: { entered in Task { status = await verifyCode(entered) ? .success : .error } })Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
value * | string | The code so far (controlled). Shorter than length = partially filled. | |
groupLabel * | string | Accessible name for the whole row. Each cell derives digit N of M from it. | |
onValueChange | (value: string) => void | Fires on every keystroke / paste with the full string so far. | |
onComplete | (value: string) => void | Fires once the final cell is filled, the hook for server-side verification. | |
length | number | 6 | Number 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). |
message | string | The single line under the row (helper / error / success). Wired via aria-describedby; live region. | |
groupAfter | number | Insert a centered separator after this many cells (e.g. 3 → a 3·3 grouped code). | |
mask | boolean | false | Render each filled cell as a dot (a password-style passcode). |
numeric | boolean | true | Restrict to digits: sets inputMode="numeric" and strips non-digits. |
disabled | boolean | false | Inert and clearly seen (never faded to invisible). |
No packaged NockerlOtpInput ships yet. These are the parameters of the real
building block (OutlinedTextField per cell) that the idiom above composes, plus the
state a future component would own.
| Parameter | Type | Default | Description |
|---|---|---|---|
value * | String | The single character for this cell (code.getOrNull(i)). | |
onValueChange * | (String) -> Unit | Per-cell change; filter to one digit and focusManager.moveFocus(Next) to auto-advance. | |
shape | Shape | NockerlControlShape | The shared 12dp control radius. Read the token, never a raw RoundedCornerShape(n). |
keyboardOptions | KeyboardOptions | NumberPassword + Next | Numeric keypad; imeAction = Next on all but the last cell (Done). |
isError | Boolean | false | Recolors the border via colorScheme.error + drives the supporting caption. |
enabled | Boolean | true | When false, the cell is dimmed (alpha) and non-interactive. |
modifier | Modifier | Modifier.width(48.dp) | Per-cell width so the row stays evenly spaced. |
Not implemented in Nockerl Voice. This is the proposed NockerlOtpInput API,
designed from the field + validation vocabulary, not a shipped signature.
| Style | Type | Default | Description |
|---|---|---|---|
code * | Binding<String> | Two-way binding to the entered code (controlled). | |
length | Int | 6 | Number of cells. |
groupLabel * | String | Accessibility label for the group; each cell exposes its index. | |
status | OtpStatus | .idle | Enum: .idle / .validating / .error / .success. Drives cell tint, glyph, message tone. |
message | String? | nil | The line under the row; announced on change. |
onComplete | (String) -> Void | { } | Called when the final cell fills, the verification hook. |