Pre-alphaThe registry and the CLI are not published yet.Roadmap

Passcode

Complete experience for entering a secret code.

Enter passcode

1 

2ABC

3DEF

4GHI

5JKL

6MNO

7PQRS

8TUV

9WXYZ

0

Installation

npx axiom add passcode
pnpm dlx axiom add passcode
yarn dlx axiom add passcode
bun x axiom add passcode

Also copies: icon, text, tappable and useControllableState. Installs react-native-reanimated.

Requires the backspace and biometrics icons in your icon registry. Colors come from passcode.slot.{dot,box} and passcode.key.{round,flat} in the component tokens.

Anatomy

Passcode is a compound component built from:

PartRole
GroupRow of code slots
SlotOne digit of the code
KeyboardIn-app keypad
KeyA digit key
KeyActionA non-digit key, such as delete

Usage

import { Passcode } from "@/components/ui/passcode";

<Passcode length={4} value={code} onChange={setCode} onComplete={unlock}>
	<Passcode.Group>
		{Array.from({ length: 4 }, (_, i) => (
			<Passcode.Slot key={i} index={i} />
		))}
	</Passcode.Group>

	<Passcode.Keyboard>
		{["1", "2", "3", "4", "5", "6", "7", "8", "9"].map((d) => (
			<Passcode.Key key={d} value={d} />
		))}
		<Passcode.KeyAction action="biometrics" onPress={useFaceId} />
		<Passcode.Key value="0" />
		<Passcode.KeyAction action="delete" />
	</Passcode.Keyboard>
</Passcode>;

Props

Passcode

PropTypeDefaultDescription
lengthnumber4Number of digits.
valuestring—The code typed so far. Use with onChange for a controlled passcode.
defaultValuestring''Initial code when the passcode manages its own state.
onChange(value: string) => void—Called on each key press and deletion.
onComplete(value: string) => void | boolean | Promise<boolean | void>—Called when the last digit is typed. Return false, or a promise of false, to play the error animation and clear the code. A promise puts the keypad in verifying until it settles.
status'idle' | 'verifying' | 'error' | 'success''idle'Drives the slots: verifying blocks the keypad, error shakes and turns the slots red, success fills them green.
disabledbooleanfalseBlocks the keypad, like verifying, and dims the keys.
hapticHapticKind | false'error'Haptic played when the status becomes error. false turns it off.
securebooleantrueSlots show filled dots. false shows the digits, for a code the user reads from somewhere.
childrenReactNode—Group and Keyboard, in any layout. Left out, both are rendered in order.

Passcode.Group, Passcode.Slot

PropTypeDefaultDescription
indexnumber—On Slot: position in the code.
variant'dot' | 'box''dot'On Group, applied to its slots: iOS dots, or boxes like InputOTP.
gapSpacingToken4On Group: space between slots.

Passcode.Keyboard

PropTypeDefaultDescription
variant'round' | 'flat''round'round: iOS lock screen keys. flat: full-width keys like the system number pad.
lettersbooleantrueShows ABC, DEF… under the digits.
childrenReactNode—12 keys, laid out in a 3×4 grid. Left out, the digits with a delete key.

Passcode.Key, Passcode.KeyAction

PropTypeDefaultDescription
valuestring—On Key: the digit it types.
action'delete' | 'biometrics' | 'custom'—On KeyAction: delete removes the last digit (long press clears all), biometrics draws the biometrics icon of your registry — map it to Face ID or a fingerprint as your app sees fit.
onPress() => void—On KeyAction: required for biometrics and custom.
childrenReactNode—Replaces the default content of the key.
accessibilityLabelstringdigit / actionKeys are announced as buttons; slots announce “2 of 4 digits entered”, never the digits.

Haptics

A wrong code plays an error haptic when the status becomes error, along with the shake, whether onComplete returned false or the screen set status="error" itself. Pass haptic={false} to turn it off. Keys play nothing: a tap per digit would buzz four times for one code. Nothing plays until your app connects a haptics library: see Haptics.

In-app keypad

The keypad is rendered by the app instead of relying only on the system keyboard. This keeps the layout stable, lets the keypad match the rest of the screen, and keeps digits out of keyboard suggestions and third-party keyboards.

Use cases

Unlocking the app

Four dots, round keys, and Face ID as the bottom-left key.

ALWelcome back, Ada
Forgot passcode?
<Passcode length={4} onComplete={(code) => auth.unlock(code)}>
	<Passcode.Group />
	<Passcode.Keyboard>
		{/* 1–9 */}
		<Passcode.KeyAction
			action="biometrics"
			onPress={auth.unlockWithBiometrics}
		/>
		<Passcode.Key value="0" />
		<Passcode.KeyAction action="delete" />
	</Passcode.Keyboard>
</Passcode>

Wrong code

onComplete returns false: the dots shake, turn red and clear, and the message says what’s left.

Wrong passcode
2 attempts left
<Passcode
	length={4}
	onComplete={async (code) => {
		const ok = await verifyPin(code);
		if (!ok) setAttempts((n) => n - 1);
		return ok;
	}}
>
	…
</Passcode>

Creating a PIN in two steps

Same component twice: enter, then confirm. flat keys and box slots for a setup screen.

Confirm your PIN
••
1234567890
const [first, setFirst] = useState<string | null>(null);

<Passcode
	key={first ? "confirm" : "create"}
	length={4}
	onComplete={(code) => {
		if (!first) {
			setFirst(code);
			return true;
		}
		if (code !== first) {
			setFirst(null);
			return false;
		}
		savePin(code);
		return true;
	}}
>
	<Passcode.Group variant="box" />
	<Passcode.Keyboard variant="flat" letters={false}>
		…
	</Passcode.Keyboard>
</Passcode>;