Numeric keypad with spring digit readout — haptic key press, error shake on the dot row, biometric slot for Face ID pairing
$ npx lumioui add pin-pad
import { PinPad } from "@/components/ui/pin-pad/pin-pad";
Files installed into your repo
components/ui/pin-pad/pin-pad.tsx
Registry dependencies (installed automatically)
Native packages required
@lumioui/core@latest@lumioui/ui@latestreact-native-reanimated@^4.0.0
Props9
| value | string | Currently entered digits (controlled). |
| onValueChange | (digits: string) => void | |
| maxLength? | 4 | 6 | PIN length the dots represent. Default 4. |
| onComplete? | (pin: string) => void | Full-PIN callback — fires the moment the last digit lands. |
| onBiometric? | () => void | Show the bottom-left biometric key (Face ID / fingerprint). |
| biometricIcon? | IconName | |
| error? | boolean | Error state — shakes the dot row and clears on next keypress. |
| disabled? | boolean | Lockout state — keys go dim and inert. |
| className? | string | |
PIN Padunlock.tsx
Live preview offline the relay is unreachable — check back shortly
pin-pad/variants/unlock.tsx open Numeric keypad with spring digit readout — haptic key press, error shake on the dot row, biometric slot for Face ID pairing
import { PinPadUnlock } from "@/components/ui/pin-pad/variants/unlock";
export function Example() {
return (
<PinPadUnlock />
);
}
import * as React from "react";
import { View } from "react-native";
import { PinPad } from "../pin-pad";
import { Ambient, Button, Glass, Icon, Text } from "@lumioui/ui";
/** PinPadUnlock — lock screen flow: enter 4 digits (try 0421), wrong codes shake. */
export function PinPadUnlock() {
const [pin, setPin] = React.useState("");
const [error, setError] = React.useState(false);
const [unlocked, setUnlocked] = React.useState(false);
const [authing, setAuthing] = React.useState(false);
const complete = (p: string) => {
if (p === "0421") {
setUnlocked(true);
} else {
setError(true);
setTimeout(() => { setError(false); setPin(""); }, 700);
}
};
const biometric = () => {
setAuthing(true);
setTimeout(() => { setAuthing(false); setUnlocked(true); }, 900);
};
if (unlocked) {
return (
<View className="relative w-full overflow-hidden rounded-3xl">
<Ambient seed="pin-pad-unlock" />
<View className="w-full gap-3 p-4">
<Glass interactive curvature="edge" className="w-full items-center gap-3 rounded-2xl p-6">
<View className="h-14 w-14 items-center justify-center rounded-full bg-success/10">
<Icon name="check-circle" size="lg" color="success" />
</View>
<Text variant="small" className="font-semibold">Welcome back</Text>
<Text variant="muted" className="text-xs text-center">Balance $4,280.50 · last sign-in Porto, 2h ago</Text>
<Button size="sm" variant="glass" onPress={() => { setUnlocked(false); setPin(""); }}>
Lock again
</Button>
</Glass>
</View>
</View>
);
}
return (
<View className="relative w-full overflow-hidden rounded-3xl">
<Ambient seed="pin-pad-unlock" />
<View className="w-full gap-3 p-4">
<Glass interactive curvature="edge" className="w-full items-center gap-3 rounded-2xl p-5">
<View className="h-12 w-12 items-center justify-center rounded-full bg-primary/10">
<Icon name="lock" size="md" color="primary" />
</View>
<View className="gap-0.5 items-center">
<Text variant="small" className="font-semibold">Enter your PIN</Text>
<Text variant="muted" className="text-xs">
{error ? "Wrong code — try 0421" : authing ? "Authenticating…" : "Lumo Wallet · demo PIN 0421"}
</Text>
</View>
<PinPad
value={pin}
onValueChange={setPin}
maxLength={4}
onComplete={complete}
onBiometric={biometric}
biometricIcon="scan-face"
error={error}
disabled={authing}
/>
</Glass>
</View>
</View>
);
}