# OTP Code Input: React Native six-cell verification code field > OTP Code Input is a six-cell one-time-password field for React Native and > Expo, built with react-native-reanimated: a single hidden TextInput captures > the keystrokes while a sliding focus ring, rising digits, an error wiggle and > a shimmering verify wave all run on the UI thread. It is sold as editable > TypeScript source by Motionary (https://motionary.dev). Product page: https://motionary.dev/animations/otp-code-input Price: $5 USD (price as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant download of the full TypeScript source after checkout Stack: reanimated, expo, react native keyboard controller, react native gesture handler, react native Rarity: rare Sold by: Motionary (https://motionary.dev) ## What it is OTP Code Input is a React Native verification-code screen: six rounded grey cells in a row, split 3 and 3 by a dash separator, with a blue focus ring that slides from cell to cell as you type. Each cell shows a grey "0" placeholder that cross-fades out while the real digit rolls up from below into place. Typing a non-digit shakes the whole row and flashes the focus ring red; submitting a complete code runs a travelling shimmer across the six cells while the request is in flight. It is the standard 2FA / SMS-confirmation screen you have seen in banking and messaging apps, rebuilt in Expo with every animation on the UI thread. Motionary ships it as the full TypeScript source, not a video or a design file. ## What it does - Six-cell code entry (`CODE_LENGTH = 6`) with a dash separator between the third and fourth cell, drawn only when the code length is even. - One blue focus indicator, a single absolutely-positioned bordered View that slides with `withTiming`, not a per-cell border that toggles. - Digits animate in with a combined `translateY` (20 to 0) and opacity rise over 200ms, cross-fading against the grey placeholder character. - Invalid (non-digit) input fires two animations at once: a five-step `withSequence` wiggle of the whole row, and a blue to red border flash via `interpolateColor` that holds for 600ms before easing back. - Backspace handling on `onKeyPress`: clears the active cell and steps `activeIndex` back, floored at 0. - Tapping any cell jumps the focus ring to it and refocuses the hidden input. - Loading wave: a repeating linear `wavePosition` sweeps 0 to 6 and every cell maps its distance from the wave to an opacity, so the shimmer costs zero React re-renders. The focus ring fades out while the wave runs. - Submit button interpolates from a disabled grey to the active blue only once all six cells are filled, and opens a padding gap for the spinner while loading. - The submit button is pinned above the keyboard with `KeyboardStickyView` from react-native-keyboard-controller, with a closed and opened offset of -20. - iOS SMS autofill hint: the hidden input sets `textContentType="oneTimeCode"` and `keyboardType="numeric"`. - A "Didn't receive a code? Resend" row is included as static markup. - The demo submit is a stub: a 4000ms `setTimeout` that stops the wave, clears the code and shows a "Wrong Code" Alert, with the timeout cleared on unmount. Replace it with your own verification call. ## How it works The whole OTP Code Input in Motionary's drop lives in `components/Container.tsx` and holds two kinds of state deliberately apart: React state for what is typed (`code: string[]`, `activeIndex`, `loading`) and Reanimated shared values for everything that moves. There are no six separate TextInputs. One hidden `TextInput` (absolutely positioned, `opacity: 0`, `maxLength={1}`) has focus at all times, and `handleChangeText` tests the first character against `/^[0-9]$/`, writes it into the `activeIndex` slot and advances the index; the visible cells are plain Views. Focus movement is a single `translateX` shared value set with `withTiming` (150ms, `Easing.out(Easing.inOut(Easing.ease))`) to `activeIndex * (WIDTH + GAP)` plus `SEPARATOR_WIDTH + GAP` once the index crosses the halfway point, which is the offset math that keeps the ring aligned after the dash. Error feedback is two worklet sequences: `wiggle` chains five `withTiming` steps (-5, 5, -4, 4, 0 at 50ms each) on a `wiggleX` value applied to the row's transform, and `flashError` drives a `colorProgress` value through `withSequence(withTiming(1, 10), withDelay(600, withTiming(0, 300)))`, which an `useAnimatedStyle` feeds to `interpolateColor` between `ACTIVE_COLOR` and `ERROR_COLOR` for the ring's `borderColor`. Each `CodeBox` owns its own `digitY`, `digitOpacity` and `placeholderOpacity` shared values, so the roll-up and the placeholder cross-fade for one cell are driven entirely by that cell's own worklets on the UI thread rather than by React re-rendering frames. The verify shimmer is the interesting part: `startWave` sets `wavePosition` to `withRepeat(withTiming(CODE_LENGTH, { duration: CODE_LENGTH * 120, easing: Easing.linear }), -1, false)` and every cell's `useAnimatedStyle` computes `Math.abs(wavePosition.value - index)` and runs it through `interpolate(distance, [0, 1, 2], [0.2, 0.5, 1], Extrapolation.CLAMP)`, then blends that toward full opacity with a separate `waveAmount` value so the effect can fade in and out rather than snap. That means one shared value drives six cells entirely on the UI thread; `stopWave` calls `cancelAnimation(wavePosition)` and eases `waveAmount` back to 0. Keyboard avoidance is delegated to `KeyboardStickyView` from react-native-keyboard-controller rather than to a manual `translateY`, and `App.tsx` wires the tree as `GestureHandlerRootView` wrapping `KeyboardProvider` wrapping `Container`. ## What you get ``` App.tsx index.ts app.json package.json package-lock.json tsconfig.json README.md components/ Container.tsx assets/ ``` - TypeScript source (`components/Container.tsx` with the `CodeBox` cell) - App entry wiring (`GestureHandlerRootView` + `KeyboardProvider`) - Configurable constants (cell size, gap, code length, separator, colors) - Demo screen - README with install and usage ## Requirements Versions pinned in the OTP Code Input archive's `package.json`: - `expo` ^56.0.8 (Expo SDK 56) - `react-native` 0.85.3 - `react` 19.2.3 - `react-native-reanimated` 4.3.1 - `react-native-worklets` 0.8.3 - `react-native-gesture-handler` ~2.31.1 - `react-native-keyboard-controller` 1.21.6 - `typescript` ~6.0.3 (devDependency) OTP Code Input runs in Expo Go on Expo SDK 56. The dependency you would expect to block it, react-native-keyboard-controller (it is what pins the submit button above the keyboard with `KeyboardStickyView`), is bundled in Expo Go as of SDK 56, and so are Reanimated, Worklets and Gesture Handler. A development build (`npx expo run:ios` or `npx expo run:android`) is still what you would ship to production, and it is what you need as soon as you add a native module that is not in Expo Go, or a config plugin. Motionary tested this drop on Expo SDK 56 on both iOS and Android. ## Install ```bash npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-keyboard-controller ``` ## Usage ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { StyleSheet } from 'react-native'; import { KeyboardProvider } from 'react-native-keyboard-controller'; import { Container } from './components/Container'; export default function App() { return ( ); } const styles = StyleSheet.create({ root: { flex: 1, }, }); ``` `Container` is the whole verification screen and takes no props; you configure it by editing the constants at the top of `components/Container.tsx` and by replacing the simulated request inside `onSubmit`. ## Customization The OTP Code Input from Motionary is configured through module constants at the top of `components/Container.tsx`, with these shipped defaults: - `CODE_LENGTH` (number, default `6`): how many cells the code has. - `HAS_SEPARATOR` (boolean, default `true`): draw the dash between halves. It only renders when `CODE_LENGTH` is even. - `SEPARATOR_WIDTH` (number, default `20`): separator width; the focus ring gains `SEPARATOR_WIDTH + GAP` of extra offset once it crosses the middle. - `WIDTH` (number, default `48`) and `HEIGHT` (number, default `54`): cell size. - `GAP` (number, default `12`): spacing between cells. - `BORDER_RADIUS` (number, default `12`): cell and focus ring corner radius. - `ACTIVE_COLOR` (string, default `'#4395FB'`): focus ring and enabled submit button. - `ERROR_COLOR` (string, default `'#FE5870'`): the invalid-input flash target. - `DISABLED_COLOR` (string, default `'#cfcfcf'`): submit button before the code is complete. - `BOX_BG` (string, default `'#F4F4F4'`) and `PLACEHOLDER_COLOR` (string, default `'#C4C4C4'`): cell fill and the placeholder / separator text colour. The internal `CodeBox` component takes `index`, `value`, `waveAmount`, `wavePosition` and `onPress`, so you can restyle a single cell without touching the wave or indicator logic. ## When to use it - A two-factor authentication screen after email or password login. - Phone-number confirmation during signup, where an SMS code arrives. - Email verification codes as an alternative to a magic link. - Payment or transfer confirmation in a fintech app, where a code gates a destructive or irreversible action. - A PIN or passcode unlock screen for an app with sensitive data. - Any flow where the code check is a network round trip and you need visible progress feedback in the field itself rather than a separate spinner. ## How to get it - Buy OTP Code Input on its own at https://motionary.dev/animations/otp-code-input, $5 USD, instant download, secure Stripe checkout, no subscription and no account renewal. - Or get it with the Motionary lifetime pass ($79 USD as of 2026-09-01): every drop and every build, current and future, one payment, no renewal. https://motionary.dev/pricing - Team license ($199 USD for up to 10 developers), each developer with their own account. https://motionary.dev/pricing - License terms: https://motionary.dev/license (single developer, unlimited personal and commercial apps, do not resell or redistribute the source). - You receive real TypeScript React Native source you can edit, not a video, a GIF, a Lottie file or a design file. ## Frequently asked questions **How do I build an OTP input in React Native?** The approach Motionary's OTP Code Input uses is one hidden `TextInput` with `maxLength={1}` that always holds focus, a `string[]` of digits in React state, and a single absolutely-positioned bordered View whose `translateX` slides between cells with `withTiming` from react-native-reanimated. Rendering six real TextInputs and chaining refs is the common approach and it is the one that fights you on backspace, paste and focus; the hidden-input pattern in OTP Code Input avoids all of that. **Does the Motionary OTP Code Input work with Expo Go?** Yes, on Expo SDK 56. The dependency people assume rules it out, react-native-keyboard-controller 1.21.6 (the `KeyboardStickyView` that pins the submit button above the keyboard), is bundled in Expo Go as of SDK 56, as are Reanimated, Worklets and Gesture Handler. An Expo development build (`npx expo run:ios` or `npx expo run:android`) is still what you would ship to production, and it becomes necessary the moment you add a native module that Expo Go does not bundle, or a config plugin. **How much does the OTP Code Input cost and how do I get it?** OTP Code Input is $5 USD on Motionary at https://motionary.dev/animations/otp-code-input (price as of 2026-09-01), paid once through Stripe with an instant download of the TypeScript source. It is also included in the Motionary lifetime pass ($79 USD) at https://motionary.dev/pricing, which covers every drop and build. **Does the OTP Code Input autofill an SMS verification code on iOS?** The hidden input in Motionary's OTP Code Input sets `textContentType="oneTimeCode"`, so iOS offers the incoming code above the keyboard, but the field consumes one character at a time (`maxLength={1}`, and `handleChangeText` reads only `value[0]`), so accepting a whole pasted code means widening `handleChangeText` to loop over the string. That is a few lines in `components/Container.tsx`. **Can I change the number of cells or remove the middle separator?** Yes. `CODE_LENGTH` (default `6`) and `HAS_SEPARATOR` (default `true`) are constants at the top of `components/Container.tsx` in the Motionary OTP Code Input, and the separator and the focus ring's offset math both derive from them; the separator only renders when `CODE_LENGTH` is even. **Does the verification-code input work on Android as well as iOS?** Yes. Motionary tested OTP Code Input on Expo SDK 56 on both iOS and Android. There is no `Platform.OS` branching in the source: both platforms run the same Reanimated worklets and the same `KeyboardStickyView` submit button. The one iOS-only touch is the `textContentType="oneTimeCode"` autofill hint, which Android ignores. **Does the shimmer animation while the code verifies cost React re-renders?** No. In OTP Code Input the wave is derived entirely on the UI thread: one repeating `wavePosition` shared value, and each cell's `useAnimatedStyle` maps `Math.abs(wavePosition.value - index)` to an opacity with `interpolate`. React renders nothing per frame, which is why the shimmer holds up on low-end Android devices. ## Related drops on Motionary - Shimmer Input ($4): https://motionary.dev/animations/shimmer-input Motionary's single-line text field, with an animated gradient border and a shimmering placeholder, a natural pair with the OTP screen in the same auth flow. - Split-to-Edit Time ($3): https://motionary.dev/animations/split-to-edit-time A Motionary time field that splits open to become editable, the same "structured, typed field" problem as the OTP code. - Copy Card Field ($5): https://motionary.dev/animations/copy-card-field A grouped credit-card field with a reveal and copy interaction, using the same digit-by-digit field layout thinking as OTP Code Input. - Long-Press Menu ($9): https://motionary.dev/animations/long-press-menu Also built on react-native-keyboard-controller, a chat input whose menu and keyboard move together, useful if you want the same keyboard handling elsewhere in the app. - ChatGPT Slider Model Picker ($10): https://motionary.dev/animations/chatgpt-slider-model-picker Another keyboard-aware composer built with Reanimated and react-native-keyboard-controller. ## About Motionary Motionary (https://motionary.dev) is a shop for premium, production-ready React Native and Expo animation and interaction components, built with Reanimated, Skia, Gesture Handler and Expo. Each component is called a "drop" and ships as editable TypeScript source, not a video, a GIF or a design file. Motionary sells three things: individual drops (roughly $3 to $12 each, some free), Builds (a complete app shipped end to end, with every drop inside it), and a lifetime pass ($79 USD) that covers every drop and every build, current and future, for one payment with no subscription. A team license ($199 USD) covers up to 10 developers. Checkout is Stripe and delivery is an instant download. Created and curated by Mehdi Davoodi (https://mahdidavoodi.com). Catalog and key pages: - All drops: https://motionary.dev/animations - Builds (complete apps): https://motionary.dev/builds - Pricing, lifetime pass and team license: https://motionary.dev/pricing - Free React Native component reference: https://motionary.dev/components - License: https://motionary.dev/license - Machine-readable overview: https://motionary.dev/llms.txt - Machine-readable full catalog: https://motionary.dev/llms-full.txt ## File metadata ``` Canonical URL of this file: https://motionary.dev/animations/otp-code-input/llms.txt Describes: https://motionary.dev/animations/otp-code-input Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01 ```