Skip to content

AuthForm

A complete, controlled authentication form. Two modes share the same layout: login shows email + password + optional forgot-password link; signup adds a confirmPassword field and hides the forgot link. AuthForm never validates internally — validation belongs in the Screen that owns the data.

When to use: Login screens, signup screens, password reset flows.

When not to use: General-purpose forms — build them with FormField + Button. Forms with custom field arrangements — compose FormField manually.

import { AuthForm } from '@/components';
<AuthForm
mode="login"
email={email}
onChangeEmail={setEmail}
password={password}
onChangePassword={setPassword}
emailError={errors.email}
passwordError={errors.password}
isLoading={isSubmitting}
onSubmit={handleLogin}
onForgotPassword={() => router.push('/forgot-password')}
footerText="Don't have an account?"
footerActionLabel="Sign up"
onFooterAction={() => router.push('/signup')}
/>
PropTypeDefaultDescription
mode'login' | 'signup''login'Which fields and labels to show
emailstringEmail field value (required)
onChangeEmail(v: string) => voidEmail change handler (required)
passwordstringPassword field value (required)
onChangePassword(v: string) => voidPassword change handler (required)
confirmPasswordstringConfirm password value (signup mode)
onChangeConfirmPassword(v: string) => voidConfirm change handler (signup mode)
emailErrorstringError shown under email field
passwordErrorstringError shown under password field
confirmPasswordErrorstringError shown under confirm field
isLoadingbooleanfalseShows spinner on submit button
onSubmit() => voidSubmit handler (required)
onForgotPassword() => voidForgot password link handler
socialActionsAuthFormSocialAction[][]OAuth provider buttons
footerTextstringFooter line (“Don’t have an account?”)
footerActionLabelstringFooter CTA (“Sign up”)
onFooterAction() => voidFooter CTA handler
testIDstring
interface AuthFormSocialAction {
label: string;
onPress: () => void;
}
export function LoginScreen() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({ email: '', password: '' });
const [isLoading, setIsLoading] = useState(false);
async function handleSubmit() {
const errs = { email: '', password: '' };
if (!email.includes('@')) errs.email = 'Enter a valid email address';
if (password.length < 8) errs.password = 'Must be at least 8 characters';
setErrors(errs);
if (errs.email || errs.password) return;
setIsLoading(true);
try {
await login(email, password);
router.replace('/home');
} catch (e) {
setErrors({ email: '', password: 'Incorrect email or password' });
} finally {
setIsLoading(false);
}
}
return (
<AuthLayout title="Welcome back" subtitle="Log in to continue">
<AuthForm
mode="login"
email={email} onChangeEmail={setEmail}
password={password} onChangePassword={setPassword}
emailError={errors.email}
passwordError={errors.password}
isLoading={isLoading}
onSubmit={handleSubmit}
onForgotPassword={() => router.push('/forgot-password')}
footerText="New here?"
footerActionLabel="Create account"
onFooterAction={() => router.push('/signup')}
/>
</AuthLayout>
);
}

Do: Keep validation in the Screen. Pass errors back as props.

Don’t: Add validation logic inside AuthForm — it’s a controlled, display-only organism.

Don’t: Use AuthForm for non-auth forms. Build them from FormField + Button.

  • AuthLayout — layout shell for auth screens
  • FormField — building block used inside AuthForm