Skip to content
WANCRAFT

A.183Buttons & Controls

PIN Pad

A tactile numeric keypad for PIN entry with spring-filled dots, error shake and keyboard support.

NewReactMotionSVGTailwindintermediate

Overview

PIN Pad is a 3 × 4 keypad of large, real buttons that press in under the finger, above a row of masked dots that fill on a spring as digits arrive. Backspace and Clear sit in the bottom corners, and while focus is anywhere inside the pad the physical keyboard works too: digits type, Backspace deletes and Enter submits. onComplete fires the moment the last digit lands. A controlled error prop shakes the dots, turns them red, shows a message and clears the entry, so a wrong PIN feels like a lock rejecting it. Shuffle randomises the keys on the client after mount and after every error. The entry only lives in component state; it is never rendered as text or stored, and screen readers hear "3 of 6 digits entered", never the digits.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/pin-pad.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/pin-pad.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/pin-pad.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { PinPad } from "@/components/ui/pin-pad";
import * as React from "react";

export function PinPadExample() {
  const [error, setError] = React.useState(false);
  // Demo only: verify real PINs on the server, never in the client.
  const check = (pin: string) => setError(pin !== "2468");

  return (
    <PinPad
      error={error}
      onChange={() => setError(false)}
      onComplete={check}
    />
  );
}

03Props

PropTypeDefaultDescription
lengthnumber4Digits in the PIN (clamped to 3–10).
onComplete(pin: string) => void—Fires when the last digit is entered, and on Enter while the entry is full.
onChange(pin: string) => void—Fires after every change. Reset error to false here.
errorbooleanfalseControlled. Rising to true shakes the dots, shows errorMessage and clears the entry.
errorMessagestring"Incorrect PIN. Try again."Shown and announced while error is true.
labelstring"Enter PIN"Visible heading; names the keypad group.
shufflebooleanfalseRandomise digit keys on mount and after each error (client-side only).
lettersbooleantruePhone-style letters under the digits.
shape"round" | "square""round"Key shape.
size"sm" | "md" | "lg""md"Key size: 56, 68 or 80 px.
accentstring"#FF7448"Next-dot ring, press bloom and focus ring colour.
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 520, damping: 22, mass: 0.6 }Spring for dots filling and emptying.
disabledbooleanfalseDisables every key.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the root group.

04Features

  • 013 × 4 keypad of large buttons with a press-in scale and accent bloom
  • 02Masked dots that fill on a configurable spring; the next dot is ringed in the accent
  • 03Physical keyboard: digits, Backspace and Enter while focus is inside the pad
  • 04Controlled error: shake, red dots and message, then the entry clears
  • 05Optional shuffled key order, regenerated after each error
  • 06Nothing is stored: the entry lives only in component state
motion · springmotion · layoutinput · clickinput · pressinput · keyboardinput · focus

05Accessibility

  • The pad is a role="group" named by its visible label; every key is a native button with an accessible name ("4", "Delete last digit", "Clear all digits").
  • A polite live region announces progress as "3 of 6 digits entered", never the digits, plus the error message and "PIN cleared".
  • The dots are aria-hidden; the entry is never rendered as text.
  • Tab into the pad and type digits; Backspace deletes, Enter submits a full entry and Space activates the focused key.
  • Accent-coloured focus-visible ring on every key.
  • Reduced motion removes the press scale, shake and spring: dots switch instantly and the error is shown by colour and text.

06Limitations

  • Client-side only: always verify PINs on a server. The demo compares against a visible sample PIN.