Passcode
Complete experience for entering a secret code.
Installation
npx axiom add passcodepnpm dlx axiom add passcodeyarn dlx axiom add passcodebun x axiom add passcodeAlso 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:
| Part | Role |
|---|---|
Group | Row of code slots |
Slot | One digit of the code |
Keyboard | In-app keypad |
Key | A digit key |
KeyAction | A 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
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 4 | Number of digits. |
value | string | — | The code typed so far. Use with onChange for a controlled passcode. |
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. |
secure | boolean | true | Slots show filled dots. false shows the digits, for a code the user reads from somewhere. |
children | ReactNode | — | Group and Keyboard, in any layout. Left out, both are rendered in order. |
Passcode.Group, Passcode.Slot
| Prop | Type | Default | Description |
|---|---|---|---|
index | number | — | On Slot: position in the code. |
variant | 'dot' | 'box' | 'dot' | On Group, applied to its slots: iOS dots, or boxes like InputOTP. |
gap | SpacingToken | 4 | On Group: space between slots. |
Passcode.Keyboard
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'round' | 'flat' | 'round' | round: iOS lock screen keys. flat: full-width keys like the system number pad. |
letters | boolean | true | Shows ABC, DEF… under the digits. |
children | ReactNode | — | 12 keys, laid out in a 3×4 grid. Left out, the digits with a delete key. |
Passcode.Key, Passcode.KeyAction
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | 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. |
children | ReactNode | — | Replaces the default content of the key. |
accessibilityLabel | string | digit / action | Keys 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.
<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.
<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.
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>;Related
- InputOTP, which uses the system keyboard
- Authentication blocks