Numpad

● Stable · v0.1

A large on-screen number pad for entering amounts — a cash count, a payment, a price — without the OS keyboard. Big, fixed-position keys a first-time owner can hit one-handed in a busy market: no typing, no tiny targets.

In context

9:41

Try it

Calculator mode

Set mode="calculator" to turn the pad into a working calculator — + − × ÷, %, decimal, AC, backspace, and =, with the live expression and running subtotal shown above the keys. It's there for real shop maths: total a few items, take a discount off, split a bill. It manages the running calculation itself, emits the current display via onChange, and the final result via onResult when = is pressed.

<Numpad
mode="calculator"
value={value}
onChange={setValue} // live display (subtotal as you go)
onResult={(total) => setValue(total)} // fired on '='
/>

Purpose

Money entry is the most repeated, highest-stakes input in the app, and the OS keyboard is the wrong tool for it: it's small, it covers half the screen, and a numeric layout still shows letters our users don't need. The Numpad replaces it with a fixed, oversized grid that stays in one place — the user looks down once, learns where the keys are, and never hunts again.

It is controlled and presentational: you own the value string and the component edits it, emitting the next string on every press. It does not format or display the number — pair it with your own amount label above (as in the examples).

Anatomy

  • Keys — the familiar phone grid: 19, a configurable bottom-left key, 0, and backspace. Each key is at least the comfortable 48–56dp touch target.
  • Bottom-left key00/000 for fast round amounts (default), or a decimal point when allowDecimal is set.
  • Backspace — deletes the last character; long-press clears the whole amount to 0.

Code

import { Numpad } from '@daytwo/components'
const [amount, setAmount] = useState('0')
// You render the amount; the Numpad only edits the string.
<Text>฿ {Number(amount).toLocaleString()}</Text>
<Numpad
value={amount}
onChange={setAmount}
zerosKey="000" // fast round amounts
/>

For decimals (a price, a weight), turn on allowDecimal — the bottom-left key becomes . and decimalScale caps the fractional digits:

<Numpad value={price} onChange={setPrice} allowDecimal decimalScale={2} />

The pure edit function is exported too, so a custom keypad layout can share the exact same behaviour:

import { applyNumpadKey } from '@daytwo/components'
const next = applyNumpadKey(value, '7', { maxLength: 9 })

When to use

  • For entering an amount of money — counting cash, recording a sale or expense, a loan or a payment.
  • Any full-screen or bottom-sheet flow whose main job is a number, where a dedicated pad is faster and clearer than the OS keyboard.

When NOT to use

  • For text (a name, a note) — that's an Input with the keyboard.
  • For a short, incidental number buried in a long form — a numeric Input keeps the field inline; a full pad would dominate the screen.
  • To display the amount — the Numpad edits the value; show it yourself in a big label above the pad.

The correct scenario

The "Count cash" sheet shows the running total in a large ฿ figure, with the Numpad fixed below it. The owner taps 9 · 3 · 00, watches ฿9,300 build up in place, and hits Confirm — the same pad they'll use for every payment and sale, so it's muscle memory by the second use.

Anti-patterns

Do
A big number pad with the amount shown large above it, fixed in place — the user taps and watches the figure build.
Don't
A tiny text field that summons the OS keyboard for a money amount — small targets, letters they don't need, half the screen covered.
The pad edits, your label shows

The Numpad never renders the number — it only edits the string. Always pair it with a large, legible amount display so the user sees what they're typing.

Edge cases

Empty / start
Start at '0', shown quiet (tertiary)
WhyA clear zero reads as "nothing entered yet"
Leading zeros
The pad drops them as real digits land
Why007 should become 7, not stay padded
Too many digits
maxLength (default 9) ignores further presses
WhyAmounts have a sane ceiling; don't overflow the display
Clear the field
Long-press backspace resets to '0'
WhyA fast way out of a wrong amount without many taps
Decimals
allowDecimal adds .; decimalScale caps the fraction
WhyPrices/weights need a point; cash counts don't
Localized UI
Pass fontFamily for Burmese/Thai surroundings
WhyKeeps the pad consistent with the rest of the screen

Tokens used

color.text.primarycolor.text.secondarycolor.background.tertiaryradius.mdtouchTargets.comfortabletypography.title2motion.duration.fast