DEV Community

Cover image for Build Next.js Auth Pages UI in 10 Minutes with Flexy UI
Abdul Basit
Abdul Basit

Posted on

Build Next.js Auth Pages UI in 10 Minutes with Flexy UI

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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:

  1. Input (Custom input field)
  2. Button (Custom button component)
  3. 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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Once done, your Login Page UI should be ready.

Tailwind CSS login form

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 
Enter fullscreen mode Exit fullscreen mode

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.
Tailwind CSS sign up form

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 
Enter fullscreen mode Exit fullscreen mode

Forgot Password Page UI

Once completed, your forgot password page should be ready.

Tailwind CSS forgot password form

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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)