# Microinteraction Patterns Reference ## Button States ### Loading Button ```tsx import { motion, AnimatePresence } from "framer-motion"; interface LoadingButtonProps { isLoading: boolean; children: React.ReactNode; onClick: () => void; } function LoadingButton({ isLoading, children, onClick }: LoadingButtonProps) { return ( ); } // Spinner component function Spinner({ className }: { className?: string }) { return ( ); } ``` ### Success/Error State ```tsx function SubmitButton({ onSubmit }: { onSubmit: () => Promise }) { const [state, setState] = useState<"idle" | "loading" | "success" | "error">( "idle", ); const handleClick = async () => { setState("loading"); try { await onSubmit(); setState("success"); setTimeout(() => setState("idle"), 2000); } catch { setState("error"); setTimeout(() => setState("idle"), 2000); } }; const icons = { idle: null, loading: , success: , error: , }; const colors = { idle: "bg-blue-600 hover:bg-blue-700", loading: "bg-blue-600", success: "bg-green-600", error: "bg-red-600", }; return ( {icons[state] && ( {icons[state]} )} {state === "idle" && "Submit"} {state === "loading" && "Submitting..."} {state === "success" && "Done!"} {state === "error" && "Failed"} ); } ``` ## Form Interactions ### Floating Label Input ```tsx import { useState, useId } from "react"; function FloatingInput({ label, type = "text", }: { label: string; type?: string; }) { const [value, setValue] = useState(""); const [isFocused, setIsFocused] = useState(false); const id = useId(); const isFloating = isFocused || value.length > 0; return (
setValue(e.target.value)} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} className="peer w-full px-4 py-3 border rounded-lg outline-none transition-colors focus:border-blue-600 focus:ring-2 focus:ring-blue-100" />
); } ``` ### Shake on Error ```tsx import { motion, useAnimation } from "framer-motion"; function ShakeInput({ error, ...props }: InputProps & { error?: string }) { const controls = useAnimation(); useEffect(() => { if (error) { controls.start({ x: [0, -10, 10, -10, 10, 0], transition: { duration: 0.4 }, }); } }, [error, controls]); return ( {error && ( {error} )} ); } ``` ### Character Count ```tsx function TextareaWithCount({ maxLength = 280 }: { maxLength?: number }) { const [value, setValue] = useState(""); const remaining = maxLength - value.length; const isNearLimit = remaining <= 20; const isOverLimit = remaining < 0; return (