Dots

Keypad

A numeric keypad for entering amounts and passcodes.

Amount: $0

"use client"

import * as React from "react"

Keypad is an on-screen number pad: 1 to 9 in three columns, then an optional key on the left, 0 and delete. Use it on full-screen amount entry and passcode screens, where a large pad is easier than the system keyboard. It does not hold a value: it reports each key press and you build the amount or code. Pick an Input or Input OTP instead when the number is one field in a longer form.

Installation

pnpm dlx shadcn@latest add @dots/keypad

Usage

import { Keypad } from "@/components/ui/keypad"
const [value, setValue] = React.useState("")
 
<Keypad
  aria-label="Amount keypad"
  leftAction="decimal"
  onKeyPress={(key) => setValue((current) => current + key)}
  onDelete={() => setValue((current) => current.slice(0, -1))}
/>

Features

  • Every key is a React Aria button with its own accessible name
  • The key left of 0 is a decimal point, a biometric key, or an empty cell
  • Optional physical keyboard capture: 0 to 9, . and Backspace
  • A large size with taller keys for accessibility settings
  • A disabled state for lockouts after too many attempts
  • The value lives with you, so any format or limit works

Amount

Set leftAction="decimal" for amounts. The decimal key calls onKeyPress with ".". Limit the cents and leading zeros in your own handler, as the demo at the top of this page does.

function press(key: string) {
  setValue((current) => {
    if (key === ".") return current.includes(".") ? current : (current || "0") + "."
    const [, cents] = current.split(".")
    if (cents !== undefined && cents.length >= 2) return current
    return current === "0" ? key : current + key
  })
}

Passcode

Set leftAction="biometric" on a passcode screen. The key calls onBiometric, where you start the device check.

0 of 6 digits entered. Enter your passcode

"use client"

import * as React from "react"

No left action

With the default leftAction="none" the cell left of 0 stays empty. Use it for codes that only take digits.

Code: Enter the code from the card

"use client"

import * as React from "react"

Large

Set size="lg" to make every key taller, for example when the person has turned on larger text.

Amount: $0

"use client"

import * as React from "react"

Disabled

Set isDisabled to disable every key, for example after too many wrong attempts. Say why and for how long next to the keypad. Keyboard capture also stops while disabled.

Too many attempts. Try again in 30 seconds.

"use client"

import { Keypad } from "@/components/ui/keypad"

Keyboard

Set captureKeyboard to listen to the physical keyboard while the keypad is mounted. Digits call onKeyPress, . does too when leftAction is "decimal", and Backspace calls onDelete. Key presses with Ctrl, Cmd or Alt, and presses inside inputs, selects, text areas and editable content, are ignored. Turn it on only when the keypad is the main control on the screen.

<Keypad captureKeyboard leftAction="decimal" onKeyPress={press} onDelete={remove} />

Accessibility

The keypad is a group and each key is a React Aria Button, so it works with touch, mouse, keyboard and screen readers.

  • The group is named "Keypad" by default. Set aria-label to say what it enters, such as "Amount keypad" or "Passcode keypad".
  • The decimal, biometric and delete keys have built-in names: "Decimal point", "Use biometrics" and "Delete".
  • The keypad does not show the value. Show it next to the keypad and announce changes with a polite live region. For a passcode, announce how many digits are entered, never the digits.

API Reference

Keypad

Takes div props except children and onKeyPress.

PropTypeDefaultDescription
onKeyPress(key: string) => void-Called with "0" to "9", and "." when leftAction is "decimal".
onDelete() => void-Called when delete, or Backspace with captureKeyboard, is pressed.
onBiometric() => void-Called when the biometric key is pressed.
leftAction"biometric" | "decimal" | "none""none"What sits left of 0.
size"default" | "lg""default"lg makes every key taller.
isDisabledbooleanfalseDisables every key and keyboard capture.
captureKeyboardbooleanfalseListens to 0 to 9, . and Backspace on the physical keyboard.
aria-labelstring"Keypad"Accessible name of the group.
classNamestring-Additional classes to apply to the keypad.