ComponentsForm Blocks
OTP Input
One-time password input with individual digit cells. Supports three visual variants, five sizes, five intents, masking, separator groups, numeric/alphanumeric/alphabetic input types, and full RTL/LTR support.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { OtpInput, OtpInputSeparator } from "@tessinaui/ui";{/* Default — 6-digit numeric */}
<OtpInput length={6} onComplete={(code) => console.log(code)} />
{/* Controlled */}
<OtpInput length={6} value={code} onValueChange={setCode} />
{/* 4-digit PIN */}
<OtpInput length={4} />
{/* Masked (password-style) */}
<OtpInput length={6} mask />
{/* Filled variant */}
<OtpInput length={6} variant="filled" />
{/* Underline variant */}
<OtpInput length={6} variant="underline" rounded="none" />
{/* With separator — 3 + 3 groups */}
<OtpInput
length={6}
separator={<OtpInputSeparator />}
separatorAfter={2}
/>
{/* Multiple separators */}
<OtpInput
length={6}
separator={<OtpInputSeparator>·</OtpInputSeparator>}
separatorAfter={[1, 3]}
/>
{/* Alphanumeric */}
<OtpInput length={6} type="alphanumeric" />
{/* Error state */}
<OtpInput length={6} intent="error" />
{/* Auto-focus on mount */}
<OtpInput length={6} autoFocus />
{/* RTL */}
<OtpInput length={6} dir="rtl" />Showcase
Variants
| Variant | Description |
|---|---|
outline | Individual bordered cells — the classic OTP look |
filled | Cells with a background fill; intent colours tint the fill |
underline | Bottom-border only for a minimal, form-field feel |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of input cells |
value | string | — | Controlled value |
defaultValue | string | "" | Uncontrolled initial value |
onValueChange | (value: string) => void | — | Called on every character change |
onComplete | (value: string) => void | — | Called when all cells are filled |
variant | outline | filled | underline | outline | Visual style |
size | xs | sm | md | lg | xl | md | Cell size |
intent | none | error | warning | success | info | none | Colour intent |
rounded | none | sm | md | lg | full | md | Cell corner radius |
type | numeric | alphanumeric | alphabetic | numeric | Allowed character set |
mask | boolean | false | Show bullets instead of characters |
separator | ReactNode | — | Element rendered between groups |
separatorAfter | number | number[] | — | Cell index (0-based) after which to insert the separator |
placeholder | string | — | Character shown in empty cells (e.g. ·, ○, –) |
disabled | boolean | false | Disables all cells |
readOnly | boolean | false | Cells are read-only |
autoFocus | boolean | false | Focus the first empty cell on mount |
dir | ltr | rtl | ltr | Text direction |
aria-label | string | "One-time password" | Accessible label for the group |
Keyboard navigation
| Key | Action |
|---|---|
0–9 / A–Z | Enter a character and advance to next cell |
Backspace | Clear current cell, or move back and clear previous |
Delete | Clear current cell without moving focus |
← / → | Move focus between cells |
Home | Focus first cell |
End | Focus last cell |
| Paste | Distribute clipboard content across cells from the focused position |
Accessibility
- The group container uses
role="group"with anaria-label - Each individual input has
aria-label="Digit N" autocomplete="one-time-code"is set automatically for numeric inputs- Focus is fully keyboard-navigable
- Disabled state is communicated via
disabledattribute on each input
Time Picker
Three time-picker variants — a drum-roll wheel, a typed HH:MM input field, and a button-triggered popover. Supports 12h/24h formats, seconds, configurable minute steps, all intent colours, RTL, and keyboard navigation.
File Upload
Drag-and-drop file upload with file list, progress, validation, four visual variants, five sizes, intent colours, and per-file status (idle/uploading/success/error).