Most React Tailwind login templates suffer from duplicated code, poor state management, or missing TypeScript support. In this guide, you’ll get:
✅ A TypeScript-first implementation for type safety
✅ Reusable Input and Button components to slash redundancy
✅ Production-ready features like loaders, error handling, and simulated API calls
To make the component scalable and modular, this login page relies on three components:
You can find the code for all these components below.
<Input />
: Handles labels, errors, and styling<Button />
: Supports loaders and dynamic states<DummyLogo />
: Placeholder logoMimic API calls with setTimeout
and display user-friendly errors.
Strongly typed props for inputs and buttons prevent runtime errors.
src/
├─ components/
│ ├─ Input.tsx # Reusable input component
│ └─ Button.tsx # Button with loader
└─ LoginPage.tsx # Main form UI
1import { ChangeEvent, FC } from 'react' 2 3interface InputProps { 4 type: 'text' | 'number' | 'email' | 'password' 5 label?: string 6 value: string | number 7 name: string 8 placeholder: string 9 error?: string 10 disabled?: boolean 11 onChange: (e: ChangeEvent<HTMLInputElement>) => void 12} 13 14const Input: FC<InputProps> = ({ 15 type = 'text', 16 label, 17 value, 18 name, 19 placeholder, 20 error, 21 disabled, 22 onChange, 23}) => { 24 return ( 25 <div className="mb-6"> 26 {label && ( 27 <label 28 htmlFor={name} 29 className="mb-1.5 block text-sm font-medium text-slate-700" 30 > 31 {label} 32 </label> 33 )} 34 <input 35 className={`w-full rounded-lg border border-gray-300 px-3 py-2.5 text-base text-slate-700 placeholder-gray-500 shadow-sm outline-blue-300 ${ 36 error ? 'ring-2 ring-red-200' : '' 37 }`} 38 type={type} 39 id={name} 40 value={value} 41 name={name} 42 placeholder={placeholder} 43 onChange={onChange} 44 disabled={disabled} 45 /> 46 {error && ( 47 <span role="alert" className="ml-3 mt-1 block text-sm text-red-600"> 48 {error} 49 </span> 50 )} 51 </div> 52 ) 53} 54 55export default Input
What It Does:
1import { LoaderCircle } from 'lucide-react' 2import React from 'react' 3 4type ButtonProps = { 5 text: string 6 loading?: boolean 7 disabled?: boolean 8} 9 10const Button: React.FC<ButtonProps> = ({ text, loading = false, disabled }) => { 11 return ( 12 <button 13 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" 14 type="submit" 15 disabled={disabled} 16 > 17 {!loading ? ( 18 text 19 ) : ( 20 <LoaderCircle 21 className="inline-block animate-spin text-center" 22 color="#fff" 23 /> 24 )} 25 </button> 26 ) 27} 28 29export default Button
What It Does:
1const DummyLogo = () => ( 2 <div className="mb-4 flex justify-center"> 3 <span className="text-3xl font-bold text-yellow-500">⚡</span> 4 </div> 5)
What It Does:
Feel free to use this code in your project.
Build better and faster UIs.Get the latest Tailwind UI components directly in your inbox. No spam!