Passcode
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.
valuestringThe code typed so far. Use with onChange for a controlled passcode.
onChange(value: string) => voidCalled 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.
securebooleantrueSlots show filled dots. false shows the digits, for a code the user reads from somewhere.
childrenReactNodeGroup and Keyboard, in any layout. Left out, both are rendered in order.

Passcode.Group, Passcode.Slot

PropTypeDefaultDescription
indexnumberOn 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.
childrenReactNode12 keys, laid out in a 3×4 grid. Left out, the digits with a delete key.

Passcode.Key, Passcode.KeyAction

PropTypeDefaultDescription
valuestringOn 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() => voidOn KeyAction: required for biometrics and custom.
childrenReactNodeReplaces the default content of the key.
accessibilityLabelstringdigit / actionKeys are announced as buttons; slots announce “2 of 4 digits entered”, never the digits.

Haptics

Keys have no haptic feedback yet. The library that produces it depends on the project, like the icon source, so Axiom waits to ask for it once rather than pull in one per component. Wire your own in onComplete and on the keys meanwhile.

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>;