Login Page 2
A modern, split-screen login page featuring social authentication, minimalist design, and a content-rich hero section with feature highlights.
Installation
Usage
Preview
A professional split-screen login page with Google and GitHub authentication, feature highlights, and responsive design.
Welcome to PageKit
Build beautiful, responsive pages with ease. Join thousands of creators who trust our platform to bring their ideas to life.
Lightning Fast Performance
Optimized for speed and efficiency
Secure & Reliable
Bank-level security for your data
24/7 Support
We're here to help anytime
“The best platform I've used for building modern web pages.”
— Sarah Chen, Product Designer
Features
- ✅ Split-screen layout with login form and hero section
- ✅ Social authentication (Google & GitHub)
- ✅ Email and password login with icons
- ✅ Full dark mode support
- ✅ Password visibility toggle
- ✅ Smooth Motion animations
- ✅ Feature list with checkmarks
- ✅ Testimonial section
- ✅ Responsive design (mobile-first)
- ✅ TypeScript support
- ✅ Accessible with ARIA labels
- ✅ Form validation ready
Component Structure
The login page is divided into two main sections:
Left Side - Login Form
Contains the authentication form with social login options and email/password inputs.
Right Side - Hero Section
Displays marketing content, feature highlights, and testimonials (visible on desktop only).
Background Pattern
Subtle dot pattern creates visual interest:
<div className="absolute inset-0 bg-gray-50 dark:bg-gray-950">
<div className="absolute inset-0 opacity-5 dark:opacity-10">
<div
className="absolute inset-0"
style={{
backgroundImage: 'radial-gradient(circle at 2px 2px, currentColor 1px, transparent 0)',
backgroundSize: '40px 40px',
}}
/>
</div>
</div>Social Login Buttons
Pre-built Google and GitHub authentication buttons:
{/* Google Sign-in */}
<motion.button
type="button"
className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white dark:bg-gray-900 border..."
whileTap={{ scale: 0.98 }}
>
<svg className="w-5 h-5" viewBox="0 0 24 24">
{/* Google SVG paths */}
</svg>
Continue with Google
</motion.button>
{/* GitHub Sign-in */}
<motion.button
type="button"
className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-gray-900 dark:bg-white..."
whileTap={{ scale: 0.98 }}
>
<Github className="w-5 h-5" />
Continue with GitHub
</motion.button>
Email/Password Form
Form with icon-enhanced inputs:
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full pl-10 pr-4 py-3 bg-white dark:bg-gray-900 border..."
placeholder="Enter your email"
required
/>
</div>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type={showPassword ? "text" : "password"}
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-10 pr-12 py-3..."
placeholder="Enter your password"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2..."
>
{showPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
Feature List
Animated feature highlights in the hero section:
<motion.div
className="flex items-start gap-3"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<div className="w-8 h-8 rounded-lg bg-gray-300 dark:bg-gray-700 flex items-center justify-center">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<div>
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">
Lightning Fast Performance
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Optimized for speed and efficiency
</p>
</div>
</motion.div>Customization
Color Theme
The design uses a neutral grayscale palette that adapts to any brand:
// Background
className="bg-gray-50 dark:bg-gray-950"
// Buttons (primary)
className="bg-gray-900 dark:bg-white text-white dark:text-gray-900"
// Inputs
className="bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700"
// Text colors
className="text-gray-900 dark:text-white" // Primary text
className="text-gray-600 dark:text-gray-400" // Secondary textBrand Customization
Replace "PageKit" with your brand name:
<h2 className="text-5xl font-bold text-gray-900 dark:text-white mb-6">
Welcome to YourBrand
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8">
Your custom tagline here
</p>Feature Items
Customize the feature list to match your product:
const features = [
{
title: "Your Feature 1",
description: "Feature description here",
delay: 0.4
},
{
title: "Your Feature 2",
description: "Feature description here",
delay: 0.5
},
{
title: "Your Feature 3",
description: "Feature description here",
delay: 0.6
}
];Layout Adjustments
Change the split-screen ratio:
{/* 50/50 split (default) */}
<div className="w-full lg:w-1/2">...</div>
<div className="hidden lg:flex lg:w-1/2">...</div>
{/* 40/60 split */}
<div className="w-full lg:w-2/5">...</div>
<div className="hidden lg:flex lg:w-3/5">...</div>
{/* Full width on mobile, fixed width on desktop */}
<div className="w-full lg:max-w-md">...</div>Social Authentication
Add more social providers:
{/* Apple Sign-in */}
<motion.button
type="button"
className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-black text-white border border-black rounded-lg"
whileTap={{ scale: 0.98 }}
onClick={handleAppleLogin}
>
<Apple className="w-5 h-5" />
Continue with Apple
</motion.button>
{/* Microsoft Sign-in */}
<motion.button
type="button"
className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white border border-gray-300 rounded-lg"
whileTap={{ scale: 0.98 }}
onClick={handleMicrosoftLogin}
>
<Microsoft className="w-5 h-5" />
Continue with Microsoft
</motion.button>
Form Handling
Implement authentication logic:
const LoginPage = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const handleEmailLogin = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError("");
try {
await signIn("credentials", {
email,
password,
redirect: false,
});
// Handle success
} catch (err) {
setError("Invalid credentials. Please try again.");
} finally {
setIsLoading(false);
}
};
const handleGoogleLogin = async () => {
await signIn("google", { callbackUrl: "/dashboard" });
};
const handleGithubLogin = async () => {
await signIn("github", { callbackUrl: "/dashboard" });
};
return (
// ... component JSX
);
};Add Error Handling
Display validation and authentication errors:
{error && (
<div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
)}
{/* Email validation */}
<input
type="email"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
required
onInvalid={(e) => {
e.currentTarget.setCustomValidity("Please enter a valid email address");
}}
onInput={(e) => {
e.currentTarget.setCustomValidity("");
}}
/>
Loading States
Add loading indicators during authentication:
<motion.button
type="submit"
disabled={isLoading}
className="w-full bg-gray-900 dark:bg-white text-white dark:text-gray-900 py-3 px-4 rounded-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
whileTap={{ scale: isLoading ? 1 : 0.98 }}
>
{isLoading ? (
<span className="flex items-center justify-center gap-2">
<svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Signing in...
</span>
) : (
"Sign in"
)}
</motion.button>Responsive Behavior
The component is fully responsive with a mobile-first approach:
- Mobile (< 1024px): Single column layout with login form only
- Desktop (≥ 1024px): Split-screen layout with hero section visible
- Max-width container: Content limited to 1600px for ultra-wide screens
Mobile-Only Layout
To show a simplified version on mobile:
{/* Hide social buttons on mobile */}
<div className="hidden sm:block space-y-3 mb-6">
{/* Social login buttons */}
</div>
{/* Hide divider on mobile */}
<div className="hidden sm:block relative mb-6">
{/* Or continue with email divider */}
</div>
Dark Mode
The component uses Tailwind's dark mode classes and automatically adapts to the user's system preferences.
Dark Mode Colors
// Background
dark:bg-gray-950
// Card/Input backgrounds
dark:bg-gray-900
// Borders
dark:border-gray-700
// Text
dark:text-white // Primary
dark:text-gray-300 // Secondary
dark:text-gray-400 // Tertiary
// Buttons
dark:bg-white // Primary button
dark:text-gray-900 // Primary button textDependencies
- motion - Animation library for smooth transitions
- lucide-react - Icon library (Eye, EyeOff, Mail, Lock, Github)
- clsx - Utility for conditional classNames
- tailwind-merge - Merge Tailwind classes without conflicts
Integration Examples
Next.js with NextAuth
import { signIn } from "next-auth/react"
const handleGoogleLogin = async () => {
await signIn("google", { callbackUrl: "/dashboard" })
}
const handleEmailLogin = async (e: React.FormEvent) => {
e.preventDefault()
const result = await signIn("credentials", {
email,
password,
redirect: false,
})
if (result?.error) {
setError(result.error)
} else {
router.push("/dashboard")
}
}With Form Validation (React Hook Form)
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
const loginSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(8, "Password must be at least 8 characters"),
})
type LoginFormData = z.infer<typeof loginSchema>
const LoginPage = () => {
const { register, handleSubmit, formState: { errors } } = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
})
const onSubmit = async (data: LoginFormData) => {
// Handle login
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
<input {...register("password")} type="password" />
{errors.password && <p>{errors.password.message}</p>}
</form>
)
}Accessibility
The component includes several accessibility features:
- ARIA labels on password toggle button
- Semantic HTML with proper form elements
- Focus states on all interactive elements
- Keyboard navigation support
- Screen reader friendly labels and hints
Enhanced Accessibility
{/* Add form labels for screen readers */}
<label htmlFor="email" className="sr-only">Email address</label>
<input
id="email"
type="email"
aria-describedby="email-error"
aria-invalid={!!errors.email}
/>
{errors.email && (
<p id="email-error" role="alert" className="text-red-600 text-sm mt-1">
{errors.email.message}
</p>
)}
{/* Announce loading state */}
<button type="submit" aria-busy={isLoading} aria-live="polite">
{isLoading ? "Signing in..." : "Sign in"}
</button>