- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
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-labelto 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.
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
isDisabled | boolean | false | Disables every key and keyboard capture. |
captureKeyboard | boolean | false | Listens to 0 to 9, . and Backspace on the physical keyboard. |
aria-label | string | "Keypad" | Accessible name of the group. |
className | string | - | Additional classes to apply to the keypad. |