Fields
Pin Input
A row of one-character fields for entering a short code.
When to use it: Use it for one-time passcodes, PINs, and confirmation codes.
Example
Verification code
Enter the 6-digit code from your email.
import { useState } from "react";
// Integrator: swap for `import { PinInput, PinInputField } from "@comp0/react";`
// once index.ts exports the pin-input barrel.
import { PinInput, PinInputField } from "@comp0/react";
export function Example() {
const [code, setCode] = useState("");
const [status, setStatus] = useState("Enter the 6-digit code from your email.");
return (
<div className="flex flex-col gap-2">
<p className="text-base font-medium text-zinc-900 sm:text-sm dark:text-zinc-100">
Verification code
</p>
<PinInput
aria-label="Verification code"
className="flex gap-2"
name="otp"
value={code}
onChange={(next) => {
setCode(next);
setStatus("Enter the 6-digit code from your email.");
}}
onComplete={(next) => setStatus(`Checking ${next}…`)}
>
{[1, 2, 3, 4, 5, 6].map((digit) => (
<PinInputField
key={digit}
aria-label={`Digit ${digit}`}
className="size-10 rounded border border-zinc-950/10 bg-white text-center text-lg text-zinc-950 outline-teal-600 focus-visible:outline-2 sm:size-9 sm:text-base dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-50 dark:outline-teal-400"
/>
))}
</PinInput>
<p className="text-base text-zinc-600 sm:text-sm dark:text-zinc-400">{status}</p>
</div>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
PinInput
Group that owns the joined code and moves focus between the fields. Owns a DOM element.
PinInputField
Native single-character input; its index follows the order the fields mount in. The first field advertises autocomplete="one-time-code". Owns a DOM element.
Step by step
- 1
Add the main part
Wrap one PinInputField per character in PinInput with an aria-label.
- 2
Add the supporting parts
Give each field its own aria-label; typing fills and advances, and pasting distributes the whole code.
- 3
Make the behavior clear
Wire onComplete to submit or verify as soon as the last field fills; pass name for form posts and mask for secret PINs.
Exampletsx <PinInput name="otp" onComplete={verify} aria-label="Verification code"><PinInputField aria-label="Digit 1" /><PinInputField aria-label="Digit 2" /><PinInputField aria-label="Digit 3" /><PinInputField aria-label="Digit 4" /></PinInput>
Keyboard
- ⌫
- Clears the field, or moves back when it is already empty.
- ←
- Moves to the previous field.
- →
- Moves to the next field.
- ⇥
- Leaves the code; typing a character already advances.
Forms and accessibility
Submits one hidden input named `name` carrying the joined code.
Accessibility checklist
- Name the group and every field: an aria-label on PinInput and one per PinInputField, such as Digit 1.
- The first field advertises autocomplete=one-time-code so platforms can offer the received code.
- Keep a visible way to submit as well; do not rely on onComplete alone to move people forward.
API reference
import { PinInput, PinInputField } from "@comp0/react";PinInput
DOM elementGroup that owns the joined code and moves focus between the fields.
| Prop | Type | Description |
|---|---|---|
value | string | Controlled joined code. |
defaultValue | string | Initial joined code. |
onChange | (value: string) => void | Receives the next joined code. |
onComplete | (value: string) => void | Fires once each time typing or pasting fills every field. |
type | "numeric" | "alphanumeric" | Accepted characters; "numeric" filters to digits and is the default. |
mask | boolean | Renders the fields as password inputs. |
name | string | Submits one hidden input carrying the joined code. |
form | string | Associates the hidden value with a form by id. |
disabled | boolean | Disables every field. |
aria-label | string | Names the group for assistive technology; required. |
PinInputField
DOM elementNative single-character input; its index follows the order the fields mount in. The first field advertises autocomplete="one-time-code".
| Prop | Type | Description |
|---|---|---|
aria-label | string | Names this field, such as "Digit 1"; required per field. |
className | string | Style each cell of the code. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
[data-disabled]on PinInput, PinInputField
- Entry is disabled.
:focus-visibleon PinInputField
- The field has keyboard focus.
:placeholder-shownon PinInputField
- The field is still empty when a placeholder is set.