DEV Community

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

Posted on

1

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! πŸŽ‰

Sentry image

See why 4M developers consider Sentry, β€œnot bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay