PixelOTPInput

One-time passcode input with auto-advance, paste-fill, and per-cell keyboard navigation.

  • Configurable length and numeric or alphanumeric input mode
  • Auto-advance on entry and backspace-to-previous behavior
  • Paste support distributes characters across cells
  • Optional mask mode and custom separator between cells
  • Controlled or uncontrolled via value / defaultValue
Status
stable
Category
forms
Since
v1.8.0

API

import { PixelOTPInput } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
lengthnumber6Number of cells.
valuestring—Code; leave unset for an uncontrolled input.
defaultValuestring—Initial code while uncontrolled.
onChange(next: string) => void—Called with the new code, after every edit.
onComplete(full: string) => void—Called with the code each time it comes to fill every cell.
maskbooleanfalseHides the characters, as a password field does.
variant'numeric' | 'alphanumeric'—Canonical structural variant.
type'numeric' | 'alphanumeric'—Deprecated. Use variant instead. Retained as alias for one minor.
autoFocusbooleanfalseFocuses the first cell once mounted.
separatorReact.ReactNode—Shown between two cells, hidden from assistive technology.
size'sm' | 'md' | 'lg''md'Cell size.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
namestring—Form field name — a hidden input submits the code.
disabledbooleanfalseDisables every cell.

ref points to <input>.

Accessibility

WCAG target: 2.1 AA

  • role="group" with aria-label for the cell collection
  • per-cell aria-label indicating digit index and total
  • inputmode=numeric|text matches the accepted character set
  • autocomplete="one-time-code" on the first cell for SMS autofill

Invalid characters are silently rejected based on the type prop. The completion event (onComplete, @complete in Vue, (complete) in Angular) fires once when all cells are filled.

Keyboard

KeyActionWhen
ArrowLeftFocus previous cell—
ArrowRightFocus next cell—
HomeFocus first cell—
EndFocus last cell—
BackspaceClear current cell, or move back and clear previous if empty—
PasteDistribute sanitized characters across remaining cells—

Usage

import React from 'react';
import { PixelOTPInput } from '@pxlkit/ui-kit';

export function Default() {
  const [value, setValue] = React.useState('')
  return <PixelOTPInput length={6} value={value} onChange={setValue} />
}

Examples

Default

import React from 'react';
import { PixelOTPInput } from '@pxlkit/ui-kit';

export function Default() {
  const [value, setValue] = React.useState('')
  return <PixelOTPInput length={6} value={value} onChange={setValue} />
}

4-digit Numeric

import React from 'react';
import { PixelOTPInput } from '@pxlkit/ui-kit';

export function Numeric4() {
  const [value, setValue] = React.useState('')
  return (
    <PixelOTPInput
      length={4}
      type="numeric"
      value={value}
      onChange={setValue}
    />
  )
}

Alphanumeric

import React from 'react';
import { PixelOTPInput } from '@pxlkit/ui-kit';

export function Alphanumeric() {
  const [value, setValue] = React.useState('')
  return (
    <PixelOTPInput
      length={6}
      type="alphanumeric"
      value={value}
      onChange={setValue}
    />
  )
}

Masked

import React from 'react';
import { PixelOTPInput } from '@pxlkit/ui-kit';

export function Masked() {
  const [value, setValue] = React.useState('')
  return (
    <PixelOTPInput
      length={6}
      mask
      value={value}
      onChange={setValue}
    />
  )
}

With Separator

import React from 'react';
import { PixelOTPInput } from '@pxlkit/ui-kit';

export function WithSeparator() {
  const [value, setValue] = React.useState('')
  return (
    <PixelOTPInput
      length={6}
      separator={<span>-</span>}
      value={value}
      onChange={setValue}
    />
  )
}

Related