FluxUI Pro is live - modern UI, powerful animations, zero hassle.
Components
Input

Input

Ultra-modern input component with multiple types, validation states, icons, and smooth animations for enhanced user experience.

Basic Input Types

Text Input

Email Input

Password Input

Search Input

Number Input

Phone Input

Input Variants

Default Variant

Filled Variant

Outlined Variant

Underlined Variant

Input Sizes

Input States

Normal State

Error State

Please enter a valid email address

Success State

Interactive Form Demo

Installation

1

Install the packages

npm i motion clsx tailwind-merge lucide-react
2

Add util file

lib/util.ts
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
3

Copy and paste the following code into your project

input.tsx
"use client"; import { motion, AnimatePresence } from "motion/react"; import React, { useState, useId } from "react"; import { cn } from "@/lib/utils"; import { Eye, EyeOff, Check, X } from "lucide-react"; interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> { variant?: "default" | "filled" | "outlined" | "underlined"; size?: "sm" | "md" | "lg"; label?: string; error?: string; success?: boolean; icon?: React.ReactNode; iconPosition?: "left" | "right"; showPasswordToggle?: boolean; } const Input: React.FC<InputProps> = ({ variant = "default", size = "md", label, error, success, icon, iconPosition = "left", showPasswordToggle = false, type = "text", className, id, ...props }) => { const [showPassword, setShowPassword] = useState(false); const [isFocused, setIsFocused] = useState(false); const [hasValue, setHasValue] = useState(!!props.value || !!props.defaultValue); const inputId = id || useId(); const inputType = type === "password" && showPassword ? "text" : type; const hasError = !!error; const hasSuccess = success && !hasError; const baseClasses = "relative w-full transition-all duration-200 bg-transparent border outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-50"; const variantClasses = { default: "border-gray-300 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-600 dark:focus:border-blue-400", filled: "border-transparent bg-gray-100 focus:bg-white focus:border-blue-500 focus:ring-blue-500/20 dark:bg-gray-800 dark:focus:bg-gray-900", outlined: "border-2 border-gray-300 bg-transparent focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-600", underlined: "border-b-2 border-gray-300 bg-transparent rounded-none px-0 focus:border-blue-500 focus:ring-0 dark:border-gray-600", }; const sizeClasses = { sm: "px-3 py-2 text-sm", md: "px-4 py-3 text-base", lg: "px-4 py-4 text-lg", }; const stateClasses = { error: "border-red-500 focus:border-red-500 focus:ring-red-500/20", success: "border-green-500 focus:border-green-500 focus:ring-green-500/20", }; const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => { setIsFocused(true); props.onFocus?.(e); }; const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => { setIsFocused(false); props.onBlur?.(e); }; const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setHasValue(!!e.target.value); props.onChange?.(e); }; const togglePasswordVisibility = () => { setShowPassword(!showPassword); }; return ( <motion.div className="relative" initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }} > {/* Label */} {label && ( <motion.label htmlFor={inputId} className={cn( "block text-sm font-medium mb-2 transition-colors duration-200", hasError ? "text-red-600 dark:text-red-400" : hasSuccess ? "text-green-600 dark:text-green-400" : "text-gray-700 dark:text-gray-300" )} animate={{ scale: isFocused || hasValue ? 1 : 1, y: isFocused || hasValue ? 0 : 0, }} transition={{ duration: 0.2 }} > {label} {props.required && <span className="text-red-500 ml-1">*</span>} </motion.label> )} {/* Input Container */} <div className="relative"> {/* Left Icon */} {icon && iconPosition === "left" && ( <motion.div className={cn( "absolute left-3 top-1/2 transform -translate-y-1/2 z-10", hasError ? "text-red-500" : hasSuccess ? "text-green-500" : "text-gray-400" )} animate={{ scale: isFocused ? 1.1 : 1, x: isFocused ? -2 : 0, }} transition={{ duration: 0.2 }} > {icon} </motion.div> )} {/* Input Field */} <motion.input id={inputId} type={inputType} className={cn( baseClasses, variantClasses[variant], sizeClasses[size], hasError && stateClasses.error, hasSuccess && stateClasses.success, icon && iconPosition === "left" && "pl-10", (icon && iconPosition === "right") || showPasswordToggle ? "pr-10" : "", className )} onFocus={handleFocus} onBlur={handleBlur} onChange={handleChange} animate={{ scale: isFocused ? 1.01 : 1, boxShadow: isFocused ? hasError ? "0 0 0 3px rgba(239, 68, 68, 0.1)" : hasSuccess ? "0 0 0 3px rgba(34, 197, 94, 0.1)" : "0 0 0 3px rgba(59, 130, 246, 0.1)" : "0 0 0 0px rgba(0, 0, 0, 0)", }} transition={{ duration: 0.2 }} {...props} /> {/* Right Icon or Password Toggle */} <div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2 z-10"> {/* Success Icon */} <AnimatePresence> {hasSuccess && !isFocused && ( <motion.div initial={{ scale: 0, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0, opacity: 0 }} transition={{ duration: 0.2 }} > <Check className="w-4 h-4 text-green-500" /> </motion.div> )} </AnimatePresence> {/* Error Icon */} <AnimatePresence> {hasError && !isFocused && ( <motion.div initial={{ scale: 0, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0, opacity: 0 }} transition={{ duration: 0.2 }} > <X className="w-4 h-4 text-red-500" /> </motion.div> )} </AnimatePresence> {/* Password Toggle */} {showPasswordToggle && type === "password" && ( <motion.button type="button" onClick={togglePasswordVisibility} className={cn( "p-1 rounded-md transition-colors duration-200", hasError ? "text-red-500 hover:bg-red-50 dark:hover:bg-red-950" : hasSuccess ? "text-green-500 hover:bg-green-50 dark:hover:bg-green-950" : "text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800" )} whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }} > {showPassword ? ( <EyeOff className="w-4 h-4" /> ) : ( <Eye className="w-4 h-4" /> )} </motion.button> )} {/* Right Icon */} {icon && iconPosition === "right" && !showPasswordToggle && ( <motion.div className={cn( "z-10", hasError ? "text-red-500" : hasSuccess ? "text-green-500" : "text-gray-400" )} animate={{ scale: isFocused ? 1.1 : 1, x: isFocused ? 2 : 0, }} transition={{ duration: 0.2 }} > {icon} </motion.div> )} </div> {/* Focus Ring Animation */} <motion.div className="absolute inset-0 rounded-md pointer-events-none" animate={{ boxShadow: isFocused ? hasError ? "0 0 0 2px rgba(239, 68, 68, 0.2)" : hasSuccess ? "0 0 0 2px rgba(34, 197, 94, 0.2)" : "0 0 0 2px rgba(59, 130, 246, 0.2)" : "0 0 0 0px rgba(0, 0, 0, 0)", }} transition={{ duration: 0.2 }} /> </div> {/* Error Message */} <AnimatePresence> {hasError && ( <motion.div initial={{ opacity: 0, y: -10, height: 0 }} animate={{ opacity: 1, y: 0, height: "auto" }} exit={{ opacity: 0, y: -10, height: 0 }} transition={{ duration: 0.2 }} className="mt-2 text-sm text-red-600 dark:text-red-400 flex items-center gap-1" > <X className="w-4 h-4 flex-shrink-0" /> {error} </motion.div> )} </AnimatePresence> {/* Success Message */} <AnimatePresence> {hasSuccess && !hasError && ( <motion.div initial={{ opacity: 0, y: -10, height: 0 }} animate={{ opacity: 1, y: 0, height: "auto" }} exit={{ opacity: 0, y: -10, height: 0 }} transition={{ duration: 0.2 }} className="mt-2 text-sm text-green-600 dark:text-green-400 flex items-center gap-1" > <Check className="w-4 h-4 flex-shrink-0" /> Looks good! </motion.div> )} </AnimatePresence> </motion.div> ); }; export default Input;
4

Update the import paths to match your project setup

Props

PropTypeDefaultDescription
type'text' | 'password' | 'email' | 'search' | 'number' | 'tel' | 'url''text'The input type.
variant'default' | 'filled' | 'outlined' | 'underlined''default'The visual style variant of the input.
size'sm' | 'md' | 'lg''md'The size of the input.
labelstringundefinedLabel text for the input.
placeholderstringundefinedPlaceholder text.
errorstringundefinedError message to display.
successbooleanfalseShow success state.
disabledbooleanfalseDisable the input.
requiredbooleanfalseMark input as required.
iconReact.ReactNodeundefinedIcon to display in the input.
iconPosition'left' | 'right''left'Position of the icon.
showPasswordTogglebooleanfalseShow password visibility toggle (for password type).