Morphing Input
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import { Input } from "../../../components/ui/input";
import { HugeiconsIcon } from "@hugeicons/react";
import {
ArrowRight02Icon,
UnfoldMoreIcon,
Album02Icon,
SparklesIcon,
} from "@hugeicons/core-free-icons";
interface PlaceholderConfig {
id: number;
placeholder: string;
icon: any;
}
// Change Here
const placeholderOptions: PlaceholderConfig[] = [
{ id: 1, placeholder: "Search anything...", icon: SparklesIcon },
{ id: 2, placeholder: "Generate Image", icon: Album02Icon },
];
const AnimatedPlaceholder = ({ text }: { text: string }) => {
const letters = text.split("");
return (
<motion.span className="inline-flex overflow-hidden">
{letters.map((letter, index) => (
<motion.span
key={`${text}-${index}`}
initial={{
opacity: 0,
rotateX: "80deg",
y: 8,
filter: "blur(3px)",
}}
exit={{
opacity: 0,
rotateX: "-80deg",
filter: "blur(3px)",
y: -8,
}}
animate={{
opacity: 1,
rotateX: "0deg",
y: 0,
filter: "blur(0px)",
}}
transition={{
delay: 0.015 * index,
type: "spring",
damping: 16,
stiffness: 240,
mass: 1.2,
}}
style={{
willChange: "transform",
}}
className="inline-block"
>
{letter === " " ? "\u00A0" : letter}
</motion.span>
))}
</motion.span>
);
};
const InputSwitch = () => {
const [activeIndex, setActiveIndex] = useState(0);
const [inputValue, setInputValue] = useState("");
const currentConfig = placeholderOptions[activeIndex];
const handleIconClick = () => {
setActiveIndex((prev) => (prev + 1) % placeholderOptions.length);
};
const IconComponent = currentConfig.icon;
return (
<div className="bg-muted w-full max-w-sm py-1 flex justify-center items-center rounded-full px-1">
<motion.button
className="bg-background p-2.5 px-2.5 rounded-full flex items-center justify-center gap-1.5 transition-colors overflow-hidden cursor-default shadow-sm"
onClick={handleIconClick}
whileTap={{ scale: 0.9 }}
>
<AnimatePresence mode="popLayout" initial={false}>
<motion.div
key={currentConfig.id}
exit={{
filter: "blur(5px)",
opacity: 0,
}}
initial={{
opacity: 0,
filter: "blur(5px)",
}}
animate={{
filter: "blur(0px)",
opacity: 1,
}}
transition={{
ease: "easeInOut",
duration: 0.35,
}}
className="flex items-center justify-center gap-1"
>
<HugeiconsIcon
icon={IconComponent}
className="w-5 h-5 text-foreground"
/>
</motion.div>
</AnimatePresence>
<HugeiconsIcon
icon={UnfoldMoreIcon}
className="w-3 h-3 text-muted-foreground"
/>
</motion.button>
<div className="flex-1 relative min-w-0">
{!inputValue && (
<div className="absolute left-0 top-0 w-full h-full flex items-center pointer-events-none pl-1.5 bg-transparent overflow-hidden">
<AnimatePresence mode="popLayout" initial={false}>
<motion.div
key={currentConfig.id}
className="text-sm text-muted-foreground whitespace-nowrap"
>
<AnimatedPlaceholder text={currentConfig.placeholder} />
</motion.div>
</AnimatePresence>
</div>
)}
<Input
type="text"
value={inputValue}
onChange={(e: any) => setInputValue(e.target.value)}
className="!border-0 outline-none border-none bg-transparent! m-0 !pl-1.5 text-sm focus-visible:ring-0 focus-visible:ring-offset-0 text-foreground"
/>
</div>
<button className="bg-background py-2.5 px-3 rounded-full flex shadow-sm items-center justify-center self-stretch cursor-pointer active:scale-95 transition-transform ease-in-out duration-150">
<HugeiconsIcon
icon={ArrowRight02Icon}
className="h-4 w-4 text-foreground"
/>
</button>
</div>
);
};
export default InputSwitch;
Installation
npx shadcn@latest add "https://uselayouts.com/r/morphing-input.json"Install dependencies
npm install motion lucide-react @hugeicons/react @hugeicons/core-free-iconsInstall UI components
Ensure you have the Input component from Shadcn UI installed.
npx shadcn@latest add inputCopy the code
Copy the code from the Code tab above into components/input-switch.tsx.
Usage
Customizing Content
Adjust the placeholder text and icons by modifying the placeholderOptions array:
// Change Here
const placeholderOptions = [
{ id: 1, placeholder: "Search anything...", icon: SparklesIcon },
{ id: 2, placeholder: "Generate Image", icon: Album02Icon },
// ...
];import InputSwitch from "@/components/morphing-input";
export default function Page() {
return <InputSwitch />;
}Features
- Mode Switch: Toggles between specific preset inputs (e.g., Search, Image Gen) and free text.
- Animated Placeholder: Placeholder text animates character-by-character.
- Search/Command Ready: Ideal for command palettes or multi-purpose search bars.
- Compact to Detailed: Expands to show more controls or details when active.
- Icon Integration: Visual icons clearly denote the current input mode.