Verify screen
ProReact Native · ExpoA complete email or SMS code verification screen: OTP cells that shake and clear on a wrong code, a resend button behind a rolling countdown, and a success check that springs in once the code is accepted.
Installation
$ npx shadcn@latest add @goodui-native-pro/verify-screenThen run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/verify-screen.
Pro screens install from the license-gated @goodui-native-pro registry. Setup: Pro access.
Usage
// app/(auth)/verify.tsx
import { router, useLocalSearchParams } from "expo-router";
import { VerifyScreen } from "@/components/goodui-native/verify-screen/verify-screen";
import { auth } from "@/lib/auth";
export default function VerifyRoute() {
const { email } = useLocalSearchParams<{ email: string }>();
return (
<VerifyScreen
channel="email"
destination={email}
onVerify={(code) => auth.verifyEmail(email, code)}
onResend={() => auth.resendEmailCode(email)}
onContinue={() => router.replace("/welcome")}
onChangeDestination={() => router.back()}
onBack={router.back}
/>
);
}When to use
- After sign-up to confirm an email address, or a phone number by SMS.
- Passwordless sign-in and two-factor steps that send a numeric code.
- Magic links without a code: use empty-state with a 'Check your inbox' message instead.
- Codes with letters: otp-input accepts digits only.
- A code field inside another form: use otp-input on its own.
Examples
SMS with a masked number
<VerifyScreen
channel="sms"
destination="+1 ••• ••• 0132"
resendAfter={60}
onVerify={(code) => api.verifyPhone(code)}
onResend={() => api.sendPhoneCode()}
onContinue={next}
/>API reference
| Prop | Type | Default |
|---|---|---|
| channel Where the code went. Changes the copy, the icon and the success title. Default `email`. | "email" | "sms" | "email" |
| destination The address or phone number the code was sent to, shown in the copy. Mask it if you like ("+1 ••• ••• 0132"). | string | channel === "email" ? "maya.patel@example.com" : "+1 (415) 555-0132" |
| length Number of digits. Default 6. | number | 6 |
| onVerify Runs when every digit is in. Return a promise: resolve to accept (the cells ripple green, then the screen turns into a success check), reject to shake, clear and let the person type again. | (code: string) => unknown | — |
| errorText Message under the cells after a rejected code. | string | "That code didn't work. Check it and try again." |
| onResend Sends a new code. Return a promise and the Resend button shows pending, "Code sent" or an error itself. | (channel: VerifyChannel) => unknown | — |
| resendAfter Seconds before Resend unlocks, on mount and after every resend. Default 30. | number | 30 |
| expiresInMinutes Minutes the code stays valid, for the copy. Default 10. `0` leaves the sentence out. | number | 10 |
| onContinue Continue button on the success state. | () => unknown | — |
| onChangeDestination "Wrong email? / Wrong number?" link. Hidden when omitted. | () => void | — |
| onBack Shows a back button in the top bar. | () => void | — |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- Digits pop into the cells; a wrong code shakes the row and clears it; an accepted code ripples a green wave across the cells (otp-input).
- 650 ms after acceptance the form gives way to the success check: the circle springs in, the check draws and a ring pulses; Continue fades in under it.
- The countdown's seconds roll down digit by digit; at zero it gives way to the Resend button.
- Reduced motion: no wave, shake, roll or spring; the check and Continue appear in place.
- Haptic: success or error from otp-input as the code is checked, and success from the check as it lands.
- Haptic: tap, success and error from the Resend and Continue buttons.
- The countdown ticks once a second against a deadline, so it stays correct after the app was in the background.
- Cells, digits and the check animate on the UI thread inside the kit components.
Accessibility
- The headline is a header. The code field is one real TextInput labelled 'Verification code, 6 digits' with one-time-code and SMS autofill (from otp-input).
- Accepted and rejected codes are announced by otp-input.
- Resend is a button that reports busy and announces 'Code sent'; the countdown before it is plain text.
- Change email or Change number is a link with a 44 pt touch target from hitSlop.
Notes for AI
- Return the verify promise from onVerify: resolve to accept, reject to shake and clear. Do not show your own error text; pass errorText to change it.
- onResend should return the send promise; the countdown restarts after the button has shown 'Code sent'. resendAfter sets the seconds (default 30).
- Navigate in onContinue; the screen shows the success state until then.
- channel switches all copy between email and SMS; pass destination as it should be read, masked if you want.
Related
A verification code field where digits pop in, a pending check breathes, success ripples across the cells and a wrong code shakes and clears.
A success or error moment: the circle springs in, the check draws itself while a ring pulses out and the title rises in; the error variant draws a cross and shakes once. Inline or fullscreen, optionally with confetti.
A number whose digits roll in the direction of change, with digits that open and close in place and currency signs that glide instead of jumping.
A complete sign-up screen: name, email and a password with a live strength meter whose segments fill and recolor while each rule's check draws itself in, a terms checkbox and an async Create account button.