animated-otp-input
v0.1.3
Published
A fully customizable, animated OTP / verification code input for React Native
Maintainers
Readme
animated-otp-input
A fully animated, fully customizable OTP / verification-code input for React Native, built on react-native-reanimated.
- 🎬 Per-cell fill animations —
scale,bounce,fade,slide,flip, ornone - 🌀 Completion-celebration animations played once the code is fully entered —
orbit,ripple,ringSweep - 🫨 Built-in shake animation on error, plus an imperative
ref.shake() - 🔒 Secure entry mode that briefly reveals a digit, then flips it into a mask dot
- ✨ Blinking cursor and focus glow/pulse on the active cell
- 📱 Native SMS/one-time-code autofill (
textContentType="oneTimeCode"on iOS,autoComplete="sms-otp"on Android) - 🎨 Three built-in variants (
boxed,underline,circle), athemeobject, per-part style props, and arenderCellescape hatch to fully replace cell rendering - 🧩 Controlled or uncontrolled usage
Demo
Installation
npm install animated-otp-input react-native-reanimatedor
yarn add animated-otp-input react-native-reanimatedreact-native-reanimated is a peer dependency (>=3.0.0). If you haven't set it up yet, follow the Reanimated installation guide — you need react-native-reanimated/plugin added as the last entry in your babel.config.js plugins array:
module.exports = {
presets: ['babel-preset-expo'], // or your existing presets
plugins: [
// ...your other plugins
'react-native-reanimated/plugin',
],
};Quick start
import { useState } from 'react';
import { OtpInput } from 'animated-otp-input';
function VerifyScreen() {
const [code, setCode] = useState('');
return (
<OtpInput
length={6}
value={code}
onChange={setCode}
onFilled={(value) => console.log('Complete code:', value)}
autoFocus
/>
);
}Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| length | number | 6 | Number of digits. |
| value | string | — | Controlled value. |
| defaultValue | string | '' | Initial value for uncontrolled usage. |
| onChange | (value: string) => void | — | Fires on every change (typing, paste, autofill, backspace). |
| onFilled | (value: string) => void | — | Fires once the value reaches length. |
| autoFocus | boolean | false | Focus on mount. |
| disabled | boolean | false | Disable input and dim cells. |
| secureTextEntry | boolean | false | Reveal-then-mask each digit as it's typed. |
| status | 'default' \| 'success' \| 'error' | 'default' | Drives color state; 'error' triggers the shake animation. |
| variant | 'boxed' \| 'underline' \| 'circle' | 'boxed' | Built-in visual style. |
| animationType | 'scale' \| 'fade' \| 'bounce' \| 'slide' \| 'flip' \| 'none' | 'scale' | Animation played when a cell fills. |
| completionAnimationType | 'none' \| 'orbit' \| 'ripple' \| 'ringSweep' | 'none' | Celebratory animation played once when the value becomes fully filled — see Completion animations. |
| completionAnimationDuration | number | 900 | Duration of the completion animation, in ms. |
| shakeOnError | boolean | true | Whether status === 'error' triggers a shake. |
| cursorAnimation | boolean | true | Blinking cursor on the focused empty cell. |
| pulseOnFocus | boolean | true | Subtle scale/glow on the focused cell. |
| allowedCharsRegex | RegExp | digits only (or alphanumeric if keyboardType="default") | Restrict which characters are accepted. |
| gap | number | 8 | Space between cells. |
| cellSize | number | 48 | Width/height of each cell. |
| theme | OtpInputTheme | see below | Color tokens. |
| containerStyle / cellStyle / focusedCellStyle / filledCellStyle / errorCellStyle / disabledCellStyle / textStyle | StyleProp | — | Per-part style overrides. |
| renderCell | (props: OtpInputCellRenderProps) => ReactNode | — | Fully replace cell rendering. |
| keyboardType | TextInputProps['keyboardType'] | 'number-pad' | Keyboard shown to the user. |
Imperative ref
import { useRef } from 'react';
import { OtpInput, type OtpInputRef } from 'animated-otp-input';
const ref = useRef<OtpInputRef>(null);
ref.current?.focus();
ref.current?.blur();
ref.current?.clear();
ref.current?.shake(); // trigger the shake animation manually
ref.current?.playCompletionAnimation(); // manually replay the completion animationCompletion animations
Set completionAnimationType to play a one-time celebration across the whole row when the value becomes fully filled (it also fires automatically — no need to call it yourself unless you want to replay it, e.g. after an async verification succeeds):
<OtpInput
length={4}
value={code}
onChange={setCode}
completionAnimationType="ripple"
/>orbit— every cell loops through a small clockwise circle in unison, flashing an accent border together.ripple— the same clockwise loop, staggered cell-by-cell into a left-to-right wave.ringSweep— cells stay in place; a small dot sweeps clockwise around each cell's border, staggered in sequence across the row.
Theming
<OtpInput
theme={{
borderColor: '#D0D5DD',
focusedBorderColor: '#4F46E5',
filledBorderColor: '#9AA4B2',
errorColor: '#E11D48',
successColor: '#16A34A',
backgroundColor: '#FFFFFF',
focusedBackgroundColor: '#F5F4FF',
textColor: '#101828',
cursorColor: '#4F46E5',
}}
/>Fully custom cells
Use renderCell when the built-in variants and style props aren't enough:
<OtpInput
length={4}
value={value}
onChange={setValue}
renderCell={({ index, digit, isFocused, isFilled }) => (
<View
key={index}
style={[
styles.cell,
isFocused && styles.cellFocused,
isFilled && styles.cellFilled,
]}
>
<Text style={styles.cellText}>{digit}</Text>
</View>
)}
/>Example app
See example/ for a full showcase covering every variant, animation type, secure entry, error/success states, and the renderCell escape hatch.
yarn
yarn example ios # or android / webContributing
License
MIT
Made with create-react-native-library
