Customize Sign-In
Learn how to customize the sign-in page styling, validation, and behavior
Learn how to customize Plainform's sign-in page to match your brand and add custom functionality.
Goal
By the end of this recipe, you'll have customized the sign-in page with your own styling, validation rules, and behavior.
Prerequisites
- A working Plainform installation
- Basic knowledge of React and TypeScript
- Familiarity with Tailwind CSS
Plainform uses custom sign-in forms built with Clerk's useSignIn hook, not Clerk's pre-built components. This gives you full control over the UI.
Steps
Locate the Sign-In Form
The sign-in form is located at:
components/user/SignInForm.tsxThis component uses:
useSignInhook from Clerk for authentication logicreact-hook-formfor form state management- Zod for validation
- Tailwind CSS for styling
Customize Form Styling
Update the form's appearance by modifying Tailwind classes:
export function SignInForm({ isInModal = false }: ISignInFormProps) {
// ... form logic
return (
<div className="w-full flex items-center justify-center h-full flex-col">
<div className="w-full flex flex-col gap-8 max-w-96 justify-center flex-auto">
<div className="gap-8 flex flex-col w-full">
{/* Customize header */}
<StepHeader
title="Welcome Back"
description="Sign in to your account to continue"
/>
{/* OAuth buttons */}
<div className="w-full flex flex-col gap-4">
<OAuthConnection
strategy="oauth_google"
lastUsed={lastStrategy}
icon="Google"
>
Continue with Google
</OAuthConnection>
<OAuthConnection
lastUsed={lastStrategy}
strategy="oauth_github"
icon="GitHub"
>
Continue with GitHub
</OAuthConnection>
</div>
{/* Separator */}
<div className="flex gap-3 items-center text-neutral-foreground">
<Separator className="shrink" />
Or continue with email
<Separator className="shrink" />
</div>
{/* Email/password form */}
<form onSubmit={handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
</div>
</div>
<LegalReminder />
</div>
);
}Common customizations:
- Update
StepHeadertitle and description - Modify OAuth button text
- Change separator text
- Adjust spacing with
gap-*classes - Add brand colors to buttons and text
Customize Validation Rules
Update the validation schema in validationSchemas/authSchemas.ts:
import { z } from 'zod';
export const signInSchema = z.object({
identifier: z
.string()
.min(1, 'Email is required')
.email('Please enter a valid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.max(100, 'Password is too long'),
});Validation options:
- Change minimum password length
- Add custom error messages
- Add regex patterns for email format
- Add maximum length constraints
Keep validation rules consistent between sign-in and sign-up forms for better UX.
Customize Sign-In Logic
The sign-in form uses Clerk's useSignIn hook:
import { isClerkAPIResponseError } from '@clerk/nextjs/errors';
import type { ClerkAPIError } from '@clerk/types';
const onSubmit: SubmitHandler<IFormData> = async (data) => {
const { identifier, password } = data;
if (!isLoaded) return;
try {
const signInAttempt = await signIn.create({
identifier,
password,
});
if (signInAttempt.status === 'complete') {
await setActive({ session: signInAttempt.createdSessionId });
router.push('/');
window.location.reload();
}
} catch (err: unknown) {
if (!isClerkAPIResponseError(err)) return;
err.errors.forEach((error: ClerkAPIError) => {
const paramName = error.meta?.paramName as keyof IFormData | undefined;
if (paramName && error.longMessage) {
setError(paramName, {
type: 'manual',
message: error.longMessage,
});
} else if (paramName === undefined) {
toast.error(error?.message);
}
});
}
};Key points:
- Use
signIn.create()with the submitted identifier and password - Use
setActive()with the returnedcreatedSessionId - Redirect to
/and reload so server-rendered session-dependent UI refreshes - Map Clerk API errors into React Hook Form field errors when possible
Preview Your Changes
Start your development server:
npm run devNavigate to http://localhost:3000/sign-in to see your customized sign-in page.
Common Customizations
Change Form Layout
Switch to a horizontal layout for wider screens:
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-4">
{/* Left side: Form */}
<SignInForm />
</div>
<div className="hidden md:flex items-center justify-center bg-muted rounded-lg">
{/* Right side: Image or branding */}
<img src="/signin-illustration.svg" alt="Sign in" />
</div>
</div>Add More OAuth Providers
Plainform includes an OAuthConnection component for OAuth buttons. To add more providers:
import { OAuthConnection } from '@/components/user/OAuthConnection';
import { useClerk } from '@clerk/nextjs';
export function SignInForm() {
const { client } = useClerk();
const lastStrategy = client?.lastAuthenticationStrategy;
return (
<div className="w-full flex flex-col gap-4">
<OAuthConnection
strategy="oauth_google" // Required: OAuth provider strategy
icon="Google" // Required: Icon name
lastUsed={lastStrategy} // Optional: Shows "Last used" badge
>
Continue with Google {/* Required: Button text */}
</OAuthConnection>
<OAuthConnection
strategy="oauth_github"
icon="GitHub"
lastUsed={lastStrategy}
>
Continue with GitHub
</OAuthConnection>
{/* Add more providers */}
<OAuthConnection
strategy="oauth_microsoft" // Must match Clerk's strategy name exactly
icon="Microsoft"
lastUsed={lastStrategy}
>
Continue with Microsoft
</OAuthConnection>
</div>
);
}The OAuthConnection component requires these props:
Required:
strategy: OAuth provider identifier (e.g.,"oauth_google","oauth_github","oauth_microsoft")- Must exactly match Clerk's strategy names
- See Available OAuth Providers for valid values
icon: Icon name for the button (e.g.,"Google","GitHub","Microsoft")- Must exist in your
SvgFindercomponent
- Must exist in your
children: Button text content (e.g., "Continue with Google")
Optional:
lastUsed: Last authentication strategy used (shows "Last used" badge when matching)isSignedUp: Use on sign-up forms so the component starts a sign-up OAuth flowclassName: Additional CSS classes for custom styling
How it works:
- Calls
signIn.authenticateWithRedirect()on sign-in forms - Calls
signUp.authenticateWithRedirect()on sign-up forms whenisSignedUpis set - Shows loading state with local
loadingProviderstate per provider during authentication - Displays "Last used" badge for the most recently used OAuth strategy
- Redirects to
/sso-callbackto complete authentication - Uses BeatLoader spinner to indicate loading state
Available OAuth providers:
oauth_google- Googleoauth_github- GitHuboauth_microsoft- Microsoftoauth_apple- Appleoauth_facebook- Facebookoauth_linkedin- LinkedInoauth_twitter- Twitter
Enable each provider in Clerk Dashboard → Social Connections before adding the button.
Common Issues
Validation Not Working
- Verify the schema is imported correctly in
SignInForm.tsx - Check that
react-hook-formresolver is configured with Zod - Ensure form fields have correct
nameattributes matching schema
Styling Not Applied
- Clear browser cache and restart dev server
- Check Tailwind CSS classes are valid
- Verify
globals.cssis imported in root layout - Run
npm run devto rebuild Tailwind
Redirect Not Working
- Verify the redirect URL is valid and accessible
- Check middleware configuration in
proxy.ts - Ensure the target route exists in your app
OAuth Buttons Not Showing
- Verify OAuth providers are enabled in Clerk Dashboard
- Check
OAuthConnectioncomponent is imported correctly - Ensure Clerk environment variables are set
Next Steps
- Add OAuth Providers - Enable Google, GitHub, and more
- Implement Roles - Add role-based access control
- Protect Routes - Secure your application routes
Related Documentation
- Authentication Overview - Learn about Clerk integration
- Usage & Integration - Authentication patterns
- Troubleshooting - Fix common issues
How is this guide ?
Last updated on