|Kit

Pin Input

A row of single-character cells for one-time codes and PINs on Ark UI's PinInput machine — autofocus advance, paste-to-fill, masking, OTP autofill, and native form submission.

PinInput is a row of single-character cells for one-time codes, PINs, and confirmation tokens, built on Ark UI's PinInput machine. Each cell is a real <input>; the machine handles autofocus advance, paste-to-fill across cells, backspace navigation, masking, and otp autofill, and a hidden input backs form submission.

import { PinInput } from '@42-central/ui-react/pin-input';

<PinInput length={6} otp />

The value is a single string — the machine speaks string[] (one char per cell), but value / defaultValue take a string and onChange / onComplete hand one back. Like the rest of the family it carries its own label, description, error, and required — no external wrapper needed — and passing error flips the cells to the invalid (red) state. Omit them and it still composes cleanly inside a Field.

There's no single root to land a testId on — each of the length cells is its own <input>. So instead every cell gets its own `${testId}-${i}` (0-indexed). See the Testing guide.

Playground

Component

Loading preview

Props

Code

<Component
  label="Verification code"
  length={4}
  size="md"
  type="numeric"
/>

One-time code

otp opts the cells into autocomplete="one-time-code", so iOS / Android surface the SMS code above the keyboard and fill every cell with one tap.

Masked

mask hides the entered characters like a password field — for PINs that shouldn't be shoulder-surfed.

Value type

type constrains what each cell accepts: numeric (default), alphanumeric, or alphabetic. Numeric cells also hint the numeric keyboard on mobile.

Sizes

Five sizes — xs through xl — matching the rest of the family. Each cell is a square that scales with the text.

Code

<PinInput size="xs" length={4} />
<PinInput size="sm" length={4} />
<PinInput size="md" length={4} />
<PinInput size="lg" length={4} />
<PinInput size="xl" length={4} />

Colors

Like the rest of the form family, the cells are palette-independent throughout — including the focus halo, which is a fixed brand-500 per the 42 UI Kit spec. color is accepted for consistency with the rest of the kit but tints nothing here. See Colors and Focus.

Code

<PinInput color="brand" length={4} />
<PinInput color="green" length={4} />
<PinInput color="purple" length={4} />

Invalid & disabled

invalid swaps every cell's border and ring to red regardless of color (and sets aria-invalid). disabled dims and blocks the cells; readOnly keeps full contrast but rejects edits.

Code

<PinInput invalid defaultValue="12" length={4} />
<PinInput disabled defaultValue="1234" length={4} />
<PinInput readOnly defaultValue="1234" length={4} />

With label, description & error

label, description (helper text), and error are built in via Field — no external wrapper needed. Passing error flips the cells to the invalid (red) state; required adds the semantic flag and a red asterisk on the label.

Enter the 6-digit code from your authenticator app.
That code is incorrect

Code

<PinInput length={6} otp label="Verification code" description="Enter the 6-digit code from your authenticator app." />

<PinInput length={6} required label="Verification code" />

<PinInput length={6} label="Verification code" error="That code is incorrect" defaultValue="12" />

If you omit these props, the cells still compose inside an outer Field, inheriting its invalid, disabled, and readOnly from context — so an error on the field flips them red automatically.

Controlled

Drive the value from state. onChange fires on every keystroke with the joined string; onComplete fires once every cell is filled.

'use client';
import { useState } from 'react';
import { PinInput } from '@42-central/ui-react/pin-input';

export function CodeInput() {
  const [value, setValue] = useState('');
  return (
    <PinInput
      length={6}
      value={value}
      onChange={setValue}
      onComplete={(code) => verify(code)}
    />
  );
}

Forms

A name wires the hidden input into native <form> submission and FormData — the value submits as the joined string.

<form action={verify}>
  <PinInput name="code" length={6} />
</form>

API

Prop

Type

Accessibility

  • Full PinInput keyboard model from Ark: typing advances to the next cell, Backspace clears and steps back, ← / → move between cells, and pasting a code fills every cell at once.
  • Each cell is labelled (Pin input 1 of N, …) and the cells are grouped, so assistive tech announces position and length.
  • invalid sets aria-invalid on the cells. Pass label / description / error for an accessible name and error text via the built-in Field, or compose inside an outer Field instead.
  • A hidden input backs form submission; pass name to include the joined value, and otp to enable OS-level one-time-code autofill.

Changelog

v0.17.0
  • Keep the visually hidden input contained inside the control

    #265
v0.3.0
  • Show the focus ring on shells that are themselves focusable: TreeSelect, TreeMultiSelect and PinInput

    #35
v0.0.1
  • Textarea, PasswordInput, NumberInput and PinInput

On this page