Sign in screen

ProReact Native · Expo

A complete sign-in screen: email and password, forgot password, an async Sign in button that gathers into a spinner and opens into a check, or shakes together with the password field when the credentials are rejected, and Continue with Apple or Google.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add @goodui-native-pro/sign-in-screen

Then run npx expo install --fix so react-native-svg match your Expo SDK. Files land in components/goodui-native/sign-in-screen.

Pro screens install from the license-gated @goodui-native-pro registry. Setup: Pro access.

Usage

// app/(auth)/sign-in.tsx
import { router } from "expo-router";
import { SignInScreen } from "@/components/goodui-native/sign-in-screen/sign-in-screen";
import { auth } from "@/lib/auth";

export default function SignInRoute() {
  return (
    <SignInScreen
      appName="Stride"
      onSignIn={({ email, password }) => auth.signInWithPassword(email, password)}
      onSignedIn={() => router.replace("/")}
      onSocialSignIn={(provider) => auth.signInWithProvider(provider)}
      onForgotPassword={(email) => router.push({ pathname: "/reset-password", params: { email } })}
      onCreateAccount={() => router.push("/sign-up")}
      onBack={router.canGoBack() ? router.back : undefined}
    />
  );
}

When to use

Use it for
  • The sign-in route of an app with email and password accounts, optionally with Apple and Google.
  • When you want validation, pending, success and error handled for you: return your auth promise from onSignIn.
Reach for something else
  • Creating an account: use sign-up-screen, which adds a name, a password strength meter and terms.
  • Passwordless email or SMS codes: send the code yourself and use verify-screen.
  • A sign-in form inside a sheet over other content: compose text-field and button in bottom-sheet instead.

Examples

Email only, prefilled

<SignInScreen providers={[]} defaultEmail={lastEmail} onSignIn={signIn} onSignedIn={goHome} />

A person-facing error

onSignIn={async ({ email, password }) => {
  const { error } = await supabase.auth.signInWithPassword({ email, password });
  if (error) throw new Error("That email and password don't match.");
}}

API reference

PropTypeDefault
appName
Product name used in the copy ("New to Stride?").
string"Stride"
title
Headline. Sentence case.
string"Welcome back"
subtitle
One line under the headline.
string"Sign in to pick up your training plan where you left it."
logo
Mark above the headline, about 56 pt. Defaults to a soft accent tile with a simple glyph; `null` hides it.
ReactNode | null—
defaultEmail
Email to prefill, for example the last account used on this device.
string""
onSignIn
Runs when Sign in is pressed with a valid email and a password. Return a promise: while it is pending the button gathers into a spinner; resolve to show the check, reject with an `Error` whose message is person-facing ("That password is incorrect.") to shake the button and show the message under the password.
(credentials: SignInCredentials) => unknown—
onSignedIn
Called about 700 ms after `onSignIn` resolves, once the check has landed. Navigate away here.
(credentials: SignInCredentials) => void—
onSocialSignIn
"Continue with Apple / Google". Return a promise and that button shows its own pending, success and error states.
(provider: SignInProvider) => unknown—
providers
Which "Continue with" buttons to show, in order. `[]` hides them and the divider. Default apple, google.
SignInProvider[]["apple", "google"]
onForgotPassword
Forgot password link. Receives the email typed so far so the reset screen can prefill it.
(email: string) => void—
onCreateAccount
"Create an account" link in the footer. 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

  • Sign in gathers into a spinner while onSignIn is pending, then opens into a drawn check with 'Signed in'.
  • A rejected sign-in shakes the button and, at the same moment, the password field shakes as the message slides in under it.
  • Missing or malformed fields shake on their own with the reason under them; no request is made.
  • Reduced motion: no shakes and no shape change; states crossfade (handled by button and text-field).
  • Haptic: tap on each button press, success when a sign-in resolves, error when it rejects (from button).
  • Haptic: error from a field that fails validation; a rejected sign-in buzzes once from the button, not again from the field.
  • Plain state for the two fields; every animation runs on the UI thread inside the kit components.
  • Keyboard listeners exist only on Android and only while the screen is mounted; iOS uses the ScrollView's automatic keyboard insets.

Accessibility

  • The headline is a header. Fields carry their labels; errors are announced by the text fields and stay visible under them.
  • Sign in, Continue with Apple and Continue with Google are buttons that report busy and announce their result; the decorative 'or' divider is hidden from screen readers.
  • Forgot password and Create an account are links with a 44 pt touch target from hitSlop.
  • Email uses the email keyboard, the username content type and email autofill; the password field uses password autofill and a reveal toggle.

Notes for AI

  • Return the auth promise from onSignIn. Reject with an Error whose message a person can read; it is shown under the password. Do not add your own loading state or error text.
  • Navigate in onSignedIn, not inside onSignIn, so the check is visible before the route changes.
  • onSocialSignIn receives 'apple' or 'google' and should return the provider sign-in promise. providers={[]} hides the buttons and the divider.
  • Links and the back button render only when their callback is passed: onForgotPassword, onCreateAccount, onBack.
  • Render it as the whole route with the header hidden; it handles safe areas and the keyboard itself.