Keypad
ProA touch number pad for PINs, quantities and amounts: keys sized for a finger, masked dots, a rejection shake and physical keyboard support.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Reach for OTPInput when a keyboard is already there and someone is typing a code into a field. Keypad is for screens with no keyboard to rely on, such as a till, a kiosk or a door panel.
Anatomy#
import {
Keypad,
KeypadClear,
KeypadConfirm,
KeypadDelete,
KeypadDigits,
KeypadDisplay,
KeypadGrid,
KeypadKey,
} from "@/components/ui/keypad"
<Keypad>
<KeypadDisplay />
<KeypadGrid>
<KeypadDigits />
<KeypadClear />
<KeypadKey value="0" />
<KeypadDelete />
<KeypadConfirm />
</KeypadGrid>
</Keypad>Sizes#
Every size is meant for a finger. md suits a phone, lg a tablet or a till, and xl a wall panel or a kiosk used at arm's length. Leave out a part and the grid closes up: place 0 with col-start-2 when there is no clear key.
Digit order#
PIN pads and phones start at 1 on the top row. Tills and calculators start at 7, which is what cashiers reach for without looking. Pass order="calculator" for those, and set masked={false} to show the value.
Amounts#
Add decimal and a KeypadKey for the separator. The value always uses ".", so it parses with Number(), while the key and format show whatever your locale expects.
Translated labels#
Every word the pad says can be replaced, including the ones only a screen reader hears: aria-label on the root and on KeypadDelete, statusLabel on the display, and the children of KeypadClear and KeypadConfirm. Use LabelsProvider to set them once for a whole app.
Disabled#
Use disabled for a lockout after too many attempts. Every key stops responding, on screen and from a keyboard.
API reference#
Keypad
The root: holds the value and listens for a physical keyboard while focus is inside it. The value is always a string, so a leading zero in a PIN survives. Accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | The entered value, controlled. |
| defaultValue | string | "" | The entered value when uncontrolled. |
| onValueChange | (value: string) => void | - | Called on every press that changes the value. |
| maxLength | number | - | Further presses are ignored at this length. Also sets how many dots KeypadDisplay draws. |
| minLength | number | - | The length that enables KeypadConfirm. Defaults to maxLength, or 1 without one. |
| onComplete | (value: string) => void | - | Called when the value reaches maxLength. |
| onConfirm | (value: string) => void | - | Called by KeypadConfirm and the Enter key. |
| decimal | boolean | false | Accepts one ".", from a KeypadKey or the . and , keys. |
| size | "md" | "lg" | "xl" | "lg" | Key height: 56, 64 or 80px. |
| disabled | boolean | false | Locks every key. |
| hasError | boolean | false | Turns the display destructive and shakes it. |
| errorKey | string | number | - | Change it to shake again while hasError stays true. |
| captureKeyboard | boolean | false | Listens on the document, so typing works before anything is focused. Skips fields. |
| name | string | - | Adds a hidden input for forms. |
| Data attribute | Description |
|---|---|
| data-slot | "keypad". |
| data-error | Present while hasError is true. |
| data-disabled | Present while disabled is true. |
KeypadDisplay
Shows what has been entered. Screen readers hear the status line only, never the digits of a masked value.
| Prop | Type | Default | Description |
|---|---|---|---|
| masked | boolean | true | Draws dots. Set false to show the value. |
| placeholder | ReactNode | - | Shown while an unmasked value is empty. |
| format | (value: string) => ReactNode | - | Formats an unmasked value, for example as a price. |
| statusLabel | (length: number, maxLength?: number) => string | - | Replaces the English screen reader status. |
| Data attribute | Description |
|---|---|
| data-slot | "keypad-display", with "keypad-dot" or "keypad-text" inside. |
KeypadGrid
The three column grid. Scales with the root, up to the width of its size.
| Data attribute | Description |
|---|---|
| data-slot | "keypad-grid". |
KeypadDigits
The keys 1 to 9. Renders no element of its own.
| Prop | Type | Default | Description |
|---|---|---|---|
| order | "phone" | "calculator" | "phone" | 1 2 3 on the top row, or 7 8 9. |
KeypadKey
One key, built on Button. Children replace the label, so a decimal key can read , and still send ..
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | What the key appends. |
| Data attribute | Description |
|---|---|
| data-slot | "keypad-key". |
| data-value | The value. |
KeypadDelete / KeypadClear
Remove the last character, or all of them. They stay enabled while empty, so focus never falls out of the pad, and dim instead.
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label | string | "Delete" | KeypadDelete's accessible name. |
| children | ReactNode | - | The label. KeypadClear defaults to "Clear". |
| Data attribute | Description |
|---|---|
| data-slot | "keypad-delete" or "keypad-clear". |
| data-empty | Present while there is nothing to remove. |
KeypadConfirm
The primary action, across the full width. Disabled until the value reaches minLength.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | "Confirm" | The label. |
| Data attribute | Description |
|---|---|
| data-slot | "keypad-confirm". |