# 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 (