UnflowUI
Components

VerificationCode

A segmented one-time-code input — one cell per digit, with auto-advance, paste distribution, arrow-key navigation, and OTP autofill support built in.

Overview

VerificationCode renders a configurable number of single-character cells (count, default 6) backed by a single controlled value string. Typing a character auto-advances focus to the next cell; Backspace/Delete clear a cell and move focus back; ArrowLeft/ArrowRight move between cells without altering their contents; and pasting a full code distributes its characters across the cells starting from whichever one is focused.

The component also wires up a visually hidden input that mirrors the joined value, so native HTML validation (required, pattern) works out of the box — a failed validation automatically falls back to an error status if none was explicitly set. The first cell sets autoComplete="one-time-code" so mobile browsers can offer SMS OTP autofill.

Import

import { VerificationCode } from '@unflow.io/ui/components/VerificationCode';

Props

PropTypeDefaultDescription
countnumber6Number of digit cells rendered
separatorAtnumber0Index after which a visual separator is inserted, e.g. separatorAt={3} with count={6} groups the cells as 123 · 456. 0 disables the separator
valuestringControlled value; each character maps to one cell
onChange(e: ChangeEvent<HTMLInputElement>) => voidFired whenever any cell changes; e.target.value is the full joined code
onComplete(e: ChangeEvent<HTMLInputElement>) => voidFired once every cell has a character
onBlur(e: FocusEvent<HTMLInputElement>) => voidFired when focus leaves the whole group (not when it moves between cells)
labelstringOptional label rendered above the group via InputLabel
hintstringOptional helper text rendered below the group
status'valid' | 'invalid' | 'error'Visual status applied to every cell (border/focus-ring color). Set automatically to 'error' when native validation fails and no explicit status is passed
disabledbooleanfalseDisables every cell
requiredbooleanMarks the field required for native form validation (enforced via the hidden mirror input)
disableRequiredSymbolbooleanHides the required symbol next to the label
namestringName of the hidden input submitted with the surrounding <form>
patternstringRegex used to accept/reject characters while typing or pasting (defaults to alphanumeric)
inputModestring'text'inputMode applied to every cell — use 'numeric' to prefer a numeric keyboard on mobile
placeholderstring'0'Placeholder shown in each empty cell
autoFocusbooleanFocuses the first empty cell (or the last cell if all are filled) on mount
onInvalid(e: FormEvent<HTMLInputElement>) => voidFired on native validation failure (e.g. required with an incomplete value)
classNamestringClasses merged into every cell's built-in styles

Basic usage

const [value, setValue] = useState('');

<VerificationCode
  name="verification-code"
  label="Verification code"
  hint="Enter the 6-digit code we sent to your phone."
  value={value}
  onChange={(e) => setValue(e.target.value)}
/>

Grouped digits

Use separatorAt to visually split the cells into groups, e.g. a 6-digit code shown as two groups of three.

<VerificationCode
  name="verification-code-grouped"
  label="Verification code"
  count={6}
  separatorAt={3}
  value={value}
  onChange={(e) => setValue(e.target.value)}
/>

Validation status

Pass status to flag the code as invalid or in error, independently of native validation.

<VerificationCode
  name="verification-code-status"
  label="Verification code"
  hint="That code is invalid or has expired."
  status="error"
  value={value}
  onChange={(e) => setValue(e.target.value)}
/>

Disabled

<VerificationCode
  name="verification-code-disabled"
  label="Verification code"
  value="123456"
  disabled
/>

Customization (slots & slotProps)

SlotTypeDescription
separatorReactNodeOverrides the rendered separator entirely; when provided, slotProps.separator no longer applies
Slot PropPropsDescription
inputLabelOmit<IInputLabel, 'label' | 'hint' | 'required' | 'disableRequiredSymbol' | 'children'>Props forwarded to the underlying InputLabel wrapper
inputContainerBaseHTMLProps<HTMLDivElement>Props for the role="group" container wrapping the cells
separatorBaseHTMLProps<HTMLSpanElement>Props for the default separator element

Accessibility

  • The cell group is a role="group" element with aria-label={label}.
  • Each cell has its own accessible name, aria-label="Digit N of {count}".
  • aria-invalid is set on every cell when status is 'error' or 'invalid'.
  • aria-describedby links each cell to the hint text when one is provided.
  • A visually hidden input mirrors the joined value and drives native required/pattern validation, surfacing the browser's validation message as an error status automatically.
  • Full keyboard support: typing auto-advances focus, Backspace/Delete clear a cell and move focus back, ArrowLeft/ArrowRight move between cells, and Enter submits the enclosing form.
  • Pasting a full code into any cell distributes it across the remaining cells starting from that position.