Building authentication pages (login, sign-up, and forgot password) from scratch can be time-consuming. However, with Flexy UI, you can set up beautiful and fully functional authentication pages in just 10 minutes.
In this article, I'll walk you through the process of integrating Flexy UI components into a Next.js 15 application.
Step #1: Set Up a Next.js Application
First, create a new Next.js 15 project. Open your terminal and run:
npx create-next-app@latest
We will name the project nextjs-auth-ui
Note: Do not install Tailwind CSS from the terminal while creating project. We'll set it up manually.
Install Tailwind CSS & Lucide React
Follow the official Tailwind CSS installation guide for Next.js. Additionally, install Lucide React for icons or any other icon library of your choice:
npm install lucide-react
Now, let's structure the application.
Step #2: Structure the Application
For authentication pages, create the following folder structure and files inside your Next.js 15 project:
π root
βββ π app
β βββ π auth
β β βββ π login
β β β βββ page.tsx
β β βββ π signup
β β β βββ page.tsx
β β βββ π forgot-password
β β β βββ page.tsx
β
βββ π components
β βββ input.tsx
β βββ button.tsx
β βββ app-logo.tsx
This structure ensures code reusability and maintains clean organization.
Step #3: Create the Login Page
Visit Flexy UI Login Page and copy the login page code.
The provided code is written for React, but we can easily adapt it to Next.js.
Login Page Code
import DummyLogo from "@/components/app-logo"
import Button from "@/components/button"
import Input from "@/components/input"
import { Lock, Mail } from "lucide-react"
import { ChangeEvent, FormEvent, useState } from "react"
function Login() {
const [user, setUser] = useState({ email: '', password: '' })
const [errors, setErrors] = useState({ email: '', password: '' })
const [showLoader, setShowLoader] = useState(false)
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.target
setUser({ ...user, [name]: value })
setErrors({ ...errors, [name]: '' })
}
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
event.preventDefault()
let newErrors = { email: '', password: '' }
if (!user.email.trim()) {
newErrors.email = 'Please enter a valid email.'
}
if (!user.password.trim()) {
newErrors.password = 'Password cannot be empty.'
}
if (newErrors.email || newErrors.password) {
setErrors(newErrors)
return
}
setShowLoader(true)
// Mimic API request
setTimeout(() => {
setShowLoader(false)
console.log('Login successful:', user)
alert('Login successful!')
}, 2000)
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md rounded-lg bg-white p-6">
<DummyLogo />
<h2 className="mb-8 text-center text-2xl font-semibold text-gray-800">Login to Flexy UI</h2>
<form onSubmit={handleSubmit} className="">
<Input
type="email"
label="Email"
name="email"
placeholder="Please enter your email"
value={user.email}
onChange={handleChange}
error={errors.email}
icon={<Mail size={20} />}
/>
<div>
<Input
type="password"
label="Password"
name="password"
placeholder="Please enter your password"
value={user.password}
onChange={handleChange}
error={errors.password}
icon={<Lock size={20} />}
/>
{/* Forgot Password Link */}
<div className="mb-4 text-right">
<a href="/forgot-password" className="text-sm text-blue-600 hover:underline">
Forgot Password?
</a>
</div>
</div>
<Button text="Sign in" loading={showLoader} disabled={showLoader} />
</form>
{/* Sign-up Link */}
<div className="mt-4 text-center">
<span className="text-sm text-gray-600">New here? </span>
<a href="/signup" className="text-sm font-medium text-blue-600 hover:underline">
Sign up
</a>
</div>
</div>
</div>
)
}
export default Login
Now, paste the login page code from Flexy UI into auth/login/page.tsx
We will update it for Next.js once we will done with structure.
Reusable Components
The Login Page depends on three reusable components:
- Input (Custom input field)
- Button (Custom button component)
- AppLogo (Dummy logo or app logo component)
Create these components inside the components folder.
Input Compnent
import { ChangeEvent } from "react"
interface InputProps {
type: 'text' | 'number' | 'email' | 'password'
label?: string
value: string | number
name: string
placeholder: string
error?: string
disabled?: boolean
onChange: (e: ChangeEvent<HTMLInputElement>) => void
icon?: React.ReactNode
}
const Input: React.FC<InputProps> = ({
type,
name,
disabled,
placeholder,
label,
value,
onChange,
error,
icon,
...props
}) => {
return (
<div className="mb-6">
{label && (
<label htmlFor={label} className="mb-1.5 block text-sm font-medium text-[#344054]">
{label}
</label>
)}
<div className="relative flex items-center">
{icon && <span className="absolute left-3 text-[#667085]">{icon}</span>}
<input
type={type}
name={name}
id={label}
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
className={`w-full rounded-lg border border-[#D0D5DD] px-4 py-2.5 pl-10 text-gray-700 placeholder:text-[#667085] focus:border-blue-200 focus:outline-none focus:ring-2 focus:ring-blue-200 ${
error && 'ring-2 ring-red-200'
}`}
{...props}
/>
</div>
{error && <p className="ml-3 mt-1 block text-sm text-red-600">{error}</p>}
</div>
)
}
export default Input
Button Component
import { LoaderCircle } from "lucide-react"
type ButtonProps = {
text: string
loading?: boolean
disabled?: boolean
}
const Button: React.FC<ButtonProps> = ({ text, loading = false, disabled }) => {
return (
<button
className="w-full cursor-pointer rounded-lg border border-neutral-800 bg-neutral-800 px-4 py-2 text-white hover:border-gray-700 hover:bg-gray-900 disabled:cursor-not-allowed disabled:bg-gray-300 disabled:text-gray-500
"
type="submit"
disabled={disabled}>
{!loading ? (
text
) : (
<LoaderCircle className="inline-block animate-spin text-center" color="#fff" />
)}
</button>
)
}
export default Button
App Logo
const DummyLogo = () => (
<div className="mb-4 flex justify-center">
<span className="text-3xl font-bold text-yellow-500">β‘</span>
</div>
)
export default DummyLogo
Once done, your Login Page UI should be ready.
Step #4: Create the Sign-Up Page
Visit Flexy UI Sign-Up Page and copy the code. Paste it into auth/signup/page.tsx
.
import DummyLogo from "@/components/app-logo"
import Button from "@/components/button"
import Input from "@/components/input"
import { Lock, Mail, UserRound } from "lucide-react"
import { ChangeEvent, FormEvent, useState } from "react"
function Signup() {
const [user, setUser] = useState({ name: '', email: '', password: '' })
const [errors, setErrors] = useState({ name: '', email: '', password: '' })
const [showLoader, setShowLoader] = useState(false)
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.target
setUser({ ...user, [name]: value })
setErrors({ ...errors, [name]: '' })
}
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
event.preventDefault()
let newErrors = { name: '', email: '', password: '' }
if (!user.name.trim()) {
newErrors.name = 'Please enter your name.'
}
if (!user.email.trim()) {
newErrors.email = 'Please enter a valid email.'
}
if (!user.password.trim()) {
newErrors.password = 'Password cannot be empty.'
}
if (newErrors.name || newErrors.email || newErrors.password) {
setErrors(newErrors)
return
}
setShowLoader(true)
// Mimic API request
setTimeout(() => {
setShowLoader(false)
console.log('Signup successful:', user)
alert('Signup successful!')
}, 2000)
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md rounded-lg bg-white p-6">
<DummyLogo />
<h2 className="mb-8 text-center text-2xl font-semibold text-gray-800">
Sign up to Flexy UI
</h2>
<form onSubmit={handleSubmit} className="">
<Input
type="text"
label="Full Name"
name="name"
placeholder="Please enter your full name"
value={user.name}
onChange={handleChange}
error={errors.name}
icon={<UserRound size={20} />}
/>
<Input
type="email"
label="Email"
name="email"
placeholder="Please enter your email"
value={user.email}
onChange={handleChange}
error={errors.email}
icon={<Mail size={20} />}
/>
<Input
type="password"
label="Password"
name="password"
placeholder="Please enter your password"
value={user.password}
onChange={handleChange}
error={errors.password}
icon={<Lock size={20} />}
/>
<div className="mt-10">
<Button text="Create an account" loading={showLoader} disabled={showLoader} />
</div>
</form>
{/* Login Link */}
<div className="mt-4 text-center">
<span className="text-sm text-gray-600">Already have an account? </span>
<a href="/login" className="text-sm font-medium text-blue-600 hover:underline">
Log in
</a>
</div>
</div>
</div>
)
}
export default Signup
Since we already have the Input and Button components, we donβt need to recreate them. Simply import and use them on the sign-up page.
Sign-Up Page UI
Your sign-up page should now be functional with Flexy UI.
Step #5: Create the Forgot Password Page
Visit Flexy UI Forgot Password Page and copy the code. Paste it into auth/forgot-password/page.tsx
.
Just like before, import and use the existing Input and Button components.
import DummyLogo from "@/components/app-logo"
import Button from "@/components/button"
import Input from "@/components/input"
import { Mail } from "lucide-react"
import { ChangeEvent, FormEvent, useState } from "react"
const ForgotPassword = () => {
const [email, setEmail] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value)
setError('')
}
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!email.trim()) {
setError('Please enter a valid email.')
return
}
setLoading(true)
setTimeout(() => {
setLoading(false)
setSuccess(true)
}, 2000)
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="mx-3 w-full max-w-lg rounded-lg border border-green-200 p-6 sm:p-10">
<DummyLogo />
<h2 className="mb-12 text-center text-2xl font-semibold text-gray-800">Forgot Password?</h2>
{success ? (
<p className="mb-6 text-center text-green-600">
Email has been sent. Please check your inbox.
</p>
) : (
<form onSubmit={handleSubmit}>
<Input
type="email"
label="Email Address"
name="email"
placeholder="Enter your email"
value={email}
onChange={handleChange}
error={error}
icon={<Mail size={20} />}
/>
<Button text="Reset Password" loading={loading} disabled={loading} />
</form>
)}
<div className="mt-4 text-center">
<a href="/login" className="text-sm font-medium text-blue-600 hover:underline">
Back to Login
</a>
</div>
</div>
</div>
)
}
export default ForgotPassword
Forgot Password Page UI
Once completed, your forgot password page should be ready.
Step #6: Convert Components to Next.js
Since Flexy UI is designed for React, we need to make minor modifications to adapt the components for Next.js:
- Add
'use client'
at the top of components using state. - Replace
<a>
tags with Next.js Link components and link to relevant pages. - Replace
<img>
tags with Next.js Image components for optimized images.
Final Code for Auth Pages
If you followed along and need the final Next.js code, here are the routes:
route: http://localhost:3000/auth/login
Login Page Next.js Code (auth/login/page.tsx
)
'use client'
import DummyLogo from '@/components/app-logo'
import Button from '@/components/button'
import Input from '@/components/input'
import { Lock, Mail } from 'lucide-react'
import Link from 'next/link'
import { ChangeEvent, FormEvent, useState } from 'react'
function Login() {
const [user, setUser] = useState({ email: '', password: '' })
const [errors, setErrors] = useState({ email: '', password: '' })
const [showLoader, setShowLoader] = useState(false)
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.target
setUser({ ...user, [name]: value })
setErrors({ ...errors, [name]: '' })
}
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
event.preventDefault()
let newErrors = { email: '', password: '' }
if (!user.email.trim()) {
newErrors.email = 'Please enter a valid email.'
}
if (!user.password.trim()) {
newErrors.password = 'Password cannot be empty.'
}
if (newErrors.email || newErrors.password) {
setErrors(newErrors)
return
}
setShowLoader(true)
// Mimic API request
setTimeout(() => {
setShowLoader(false)
console.log('Login successful:', user)
alert('Login successful!')
}, 2000)
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md rounded-lg bg-white p-6">
<DummyLogo />
<h2 className="mb-8 text-center text-2xl font-semibold text-gray-800">Login to Flexy UI</h2>
<form onSubmit={handleSubmit} className="">
<Input
type="email"
label="Email"
name="email"
placeholder="Please enter your email"
value={user.email}
onChange={handleChange}
error={errors.email}
icon={<Mail size={20} />}
/>
<div>
<Input
type="password"
label="Password"
name="password"
placeholder="Please enter your password"
value={user.password}
onChange={handleChange}
error={errors.password}
icon={<Lock size={20} />}
/>
{/* Forgot Password Link */}
<div className="mb-4 text-right">
<Link href="/auth/forgot-password" className="text-sm text-blue-600 hover:underline">
Forgot Password?
</Link>
</div>
</div>
<Button text="Sign in" loading={showLoader} disabled={showLoader} />
</form>
{/* Sign-up Link */}
<div className="mt-4 text-center">
<span className="text-sm text-gray-600">New here? </span>
<Link href="/auth/signup" className="text-sm font-medium text-blue-600 hover:underline">
Sign up
</Link>
</div>
</div>
</div>
)
}
export default Login
Sign-Up Page Next.js Code (auth/signup/page.tsx
)
route: http://localhost:3000/auth/signup
'use client'
import DummyLogo from '@/components/app-logo'
import Button from '@/components/button'
import Input from '@/components/input'
import { Lock, Mail, UserRound } from 'lucide-react'
import Link from 'next/link'
import { ChangeEvent, FormEvent, useState } from 'react'
function Signup() {
const [user, setUser] = useState({ name: '', email: '', password: '' })
const [errors, setErrors] = useState({ name: '', email: '', password: '' })
const [showLoader, setShowLoader] = useState(false)
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.target
setUser({ ...user, [name]: value })
setErrors({ ...errors, [name]: '' })
}
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
event.preventDefault()
let newErrors = { name: '', email: '', password: '' }
if (!user.name.trim()) {
newErrors.name = 'Please enter your name.'
}
if (!user.email.trim()) {
newErrors.email = 'Please enter a valid email.'
}
if (!user.password.trim()) {
newErrors.password = 'Password cannot be empty.'
}
if (newErrors.name || newErrors.email || newErrors.password) {
setErrors(newErrors)
return
}
setShowLoader(true)
// Mimic API request
setTimeout(() => {
setShowLoader(false)
console.log('Signup successful:', user)
alert('Signup successful!')
}, 2000)
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md rounded-lg bg-white p-6">
<DummyLogo />
<h2 className="mb-8 text-center text-2xl font-semibold text-gray-800">
Sign up to Flexy UI
</h2>
<form onSubmit={handleSubmit} className="">
<Input
type="text"
label="Full Name"
name="name"
placeholder="Please enter your full name"
value={user.name}
onChange={handleChange}
error={errors.name}
icon={<UserRound size={20} />}
/>
<Input
type="email"
label="Email"
name="email"
placeholder="Please enter your email"
value={user.email}
onChange={handleChange}
error={errors.email}
icon={<Mail size={20} />}
/>
<Input
type="password"
label="Password"
name="password"
placeholder="Please enter your password"
value={user.password}
onChange={handleChange}
error={errors.password}
icon={<Lock size={20} />}
/>
<div className="mt-10">
<Button text="Create an account" loading={showLoader} disabled={showLoader} />
</div>
</form>
{/* Login Link */}
<div className="mt-4 text-center">
<span className="text-sm text-gray-600">Already have an account? </span>
<Link href="/auth/login" className="text-sm font-medium text-blue-600 hover:underline">
Log in
</Link>
</div>
</div>
</div>
)
}
export default Signup
Forgot Password Page Next.js Code (auth/forgot-password/page.tsx
)**
route: http://localhost:3000/auth/forgot-password
'use client'
import DummyLogo from '@/components/app-logo'
import Button from '@/components/button'
import Input from '@/components/input'
import { Mail } from 'lucide-react'
import Link from 'next/link'
import { ChangeEvent, FormEvent, useState } from 'react'
const ForgotPassword = () => {
const [email, setEmail] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value)
setError('')
}
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!email.trim()) {
setError('Please enter a valid email.')
return
}
setLoading(true)
setTimeout(() => {
setLoading(false)
setSuccess(true)
}, 2000)
}
return (
<div className="flex min-h-screen items-center justify-center">
<div className="mx-3 w-full max-w-lg rounded-lg border border-green-200 p-6 sm:p-10">
<DummyLogo />
<h2 className="mb-12 text-center text-2xl font-semibold text-gray-800">Forgot Password?</h2>
{success ? (
<p className="mb-6 text-center text-green-600">
Email has been sent. Please check your inbox.
</p>
) : (
<form onSubmit={handleSubmit}>
<Input
type="email"
label="Email Address"
name="email"
placeholder="Enter your email"
value={email}
onChange={handleChange}
error={error}
icon={<Mail size={20} />}
/>
<Button text="Reset Password" loading={loading} disabled={loading} />
</form>
)}
<div className="mt-4 text-center">
<Link href="/auth/login" className="text-sm font-medium text-blue-600 hover:underline">
Back to Login
</Link>
</div>
</div>
</div>
)
}
export default ForgotPassword
In just 10 minutes, you have built Next.js authentication pages using Flexy UI! π
If you are looking for professional, beautifully designed UI components, visit Flexy UI to speed up your development process.
Happy coding! π
Top comments (0)