# Verify screen

> A 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.

- Type: Component (screens)
- Access: Pro
- Page: https://goodui.dev/native/components/verify-screen
- Markdown: https://goodui.dev/native/components/verify-screen.md
- Registry item: https://goodui.dev/native/r/verify-screen.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native verify email screen, otp verification screen expo, sms code screen, resend code countdown, two factor screen, confirm email code

## 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.

## When not to use

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

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add @goodui-native-pro/verify-screen
npx expo install --fix
```

Pro: add `"@goodui-native-pro": { "url": "https://goodui.dev/native/r/pro/{name}.json", "headers": { "Authorization": "Bearer ${GOODUI_LICENSE_KEY}" } }` to the `registries` of components.json and put your license key in .env.local.

The component lands in `components/goodui-native/verify-screen/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs button, icon-button, locale, otp-input, rolling-number, success-check.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
// 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}
    />
  );
}
```

## Examples

### SMS with a masked number

```tsx
<VerifyScreen
  channel="sms"
  destination="+1 ••• ••• 0132"
  resendAfter={60}
  onVerify={(code) => api.verifyPhone(code)}
  onResend={() => api.sendPhoneCode()}
  onContinue={next}
/>
```

## API reference

### VerifyScreen

`import { VerifyScreen } from "@/components/goodui-native/verify-screen/verify-screen";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `channel` | `"email" \| "sms"` | `"email"` | Where the code went. Changes the copy, the icon and the success title. Default `email`. |
| `destination` | `string` | `channel === "email" ? "maya.patel@example.com" : "+1 (415) 555-0132"` | The address or phone number the code was sent to, shown in the copy. Mask it if you like ("+1 ••• ••• 0132"). |
| `length` | `number` | `6` | Number of digits. Default 6. |
| `onVerify` | `(code: string) => unknown` | – | 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. |
| `errorText` | `string` | `"That code didn't work. Check it and try again."` | Message under the cells after a rejected code. |
| `onResend` | `(channel: VerifyChannel) => unknown` | – | Sends a new code. Return a promise and the Resend button shows pending, "Code sent" or an error itself. |
| `resendAfter` | `number` | `30` | Seconds before Resend unlocks, on mount and after every resend. Default 30. |
| `expiresInMinutes` | `number` | `10` | Minutes the code stays valid, for the copy. Default 10. `0` leaves the sentence out. |
| `onContinue` | `() => unknown` | – | Continue button on the success state. |
| `onChangeDestination` | `() => void` | – | "Wrong email? / Wrong number?" link. Hidden when omitted. |
| `onBack` | `() => void` | – | Shows a back button in the top bar. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## 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.

## Motion

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

## Haptics

- success or error from otp-input as the code is checked, and success from the check as it lands.
- tap, success and error from the Resend and Continue buttons.

## Performance

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

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

- [OTP input](https://goodui.dev/native/components/otp-input.md): A verification code field where digits pop in, a pending check breathes, success ripples across the cells and a wrong code shakes and clears.
- [Success check](https://goodui.dev/native/components/success-check.md): 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.
- [Rolling number](https://goodui.dev/native/components/rolling-number.md): 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.
- [Sign up screen](https://goodui.dev/native/components/sign-up-screen.md): 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.

## Guidance for AI tools

Verify screen: A 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. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
