Shadcn.io is not affiliated with official shadcn/ui
React Animated Modal Button
React animated modal button with 3D perspective and backdrop blur. Framer Motion creates smooth modal transitions with TypeScript and shadcn/ui styling.
Modals are everywhere—confirmations, forms, product details, notifications. But most just fade in with a basic overlay. Flat. Uninspiring. No depth or sophistication. Meanwhile, users expect rich visual experiences that feel premium and intentional. This React component gives you modals with 3D perspective animations, spring physics, and backdrop blur that make every interaction feel polished and professional.
import { motion } from "framer-motion"import { Modal, ModalBody, ModalContent, ModalFooter, ModalTrigger,} from "~/packages/components/interactive/animated-modal"export default function AnimatedModalDemo() { const images = [ "https://images.unsplash.com/photo-1517322048670-4fba75cbbb62?q=80&w=3000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=3425&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1555400038-63f5ba517a47?q=80&w=3540&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1554931670-4ebfabf6e7a9?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", "https://images.unsplash.com/photo-1546484475-7f7bd55792da?q=80&w=2581&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", ] return ( <div className="py-40 flex items-center justify-center"> <Modal> <ModalTrigger className="bg-black dark:bg-white dark:text-black text-white flex justify-center group/modal-btn"> <span className="group-hover/modal-btn:translate-x-40 text-center transition duration-500"> Book your flight </span> <div className="-translate-x-40 group-hover/modal-btn:translate-x-0 flex items-center justify-center absolute inset-0 transition duration-500 text-white z-20"> ✈️ </div> </ModalTrigger> <ModalBody> <ModalContent> <h4 className="text-lg md:text-2xl text-neutral-600 dark:text-neutral-100 font-bold text-center mb-8"> Book your trip to{" "} <span className="px-1 py-0.5 rounded-md bg-gray-100 dark:bg-neutral-800 dark:border-neutral-700 border border-gray-200"> Bali </span>{" "} now! ✈️ </h4> <div className="flex justify-center items-center"> {images.map((image, idx) => ( <motion.div className="rounded-xl -mr-4 mt-4 p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 border border-neutral-100 shrink-0 overflow-hidden" key={`images${idx}`} style={{ rotate: Math.random() * 20 - 10, }} whileHover={{ scale: 1.1, rotate: 0, zIndex: 100, }} whileTap={{ scale: 1.1, rotate: 0, zIndex: 100, }} > <img alt="bali images" className="rounded-lg h-20 w-20 md:h-40 md:w-40 object-cover shrink-0" height="500" src={image} width="500" /> </motion.div> ))} </div> <div className="py-10 flex flex-wrap gap-x-4 gap-y-6 items-start justify-start max-w-sm mx-auto"> <div className="flex items-center justify-center"> <PlaneIcon className="mr-1 text-neutral-700 dark:text-neutral-300 h-4 w-4" /> <span className="text-neutral-700 dark:text-neutral-300 text-sm"> 5 connecting flights </span> </div> <div className="flex items-center justify-center"> <ElevatorIcon className="mr-1 text-neutral-700 dark:text-neutral-300 h-4 w-4" /> <span className="text-neutral-700 dark:text-neutral-300 text-sm">12 hotels</span> </div> <div className="flex items-center justify-center"> <VacationIcon className="mr-1 text-neutral-700 dark:text-neutral-300 h-4 w-4" /> <span className="text-neutral-700 dark:text-neutral-300 text-sm"> 69 visiting spots </span> </div> <div className="flex items-center justify-center"> <FoodIcon className="mr-1 text-neutral-700 dark:text-neutral-300 h-4 w-4" /> <span className="text-neutral-700 dark:text-neutral-300 text-sm"> Good food everyday </span> </div> <div className="flex items-center justify-center"> <MicIcon className="mr-1 text-neutral-700 dark:text-neutral-300 h-4 w-4" /> <span className="text-neutral-700 dark:text-neutral-300 text-sm">Open Mic</span> </div> <div className="flex items-center justify-center"> <ParachuteIcon className="mr-1 text-neutral-700 dark:text-neutral-300 h-4 w-4" /> <span className="text-neutral-700 dark:text-neutral-300 text-sm">Paragliding</span> </div> </div> </ModalContent> <ModalFooter className="gap-4"> <button type="button" className="px-2 py-1 bg-gray-200 text-black dark:bg-black dark:border-black dark:text-white border border-gray-300 rounded-md text-sm w-28" > Cancel </button> <button type="button" className="bg-black text-white dark:bg-white dark:text-black text-sm px-2 py-1 rounded-md border border-black w-28" > Book Now </button> </ModalFooter> </ModalBody> </Modal> </div> )}const PlaneIcon = ({ className }: { className?: string }) => { return ( <svg aria-label="Plane" className={className} fill="none" height="24" role="img" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M0 0h24v24H0z" fill="none" stroke="none" /> <path d="M16 10h4a2 2 0 0 1 0 4h-4l-4 7h-3l2 -7h-4l-2 2h-3l2 -4l-2 -4h3l2 2h4l-2 -7h3z" /> </svg> )}const VacationIcon = ({ className }: { className?: string }) => { return ( <svg aria-label="Vacation" className={className} fill="none" height="24" role="img" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M0 0h24v24H0z" fill="none" stroke="none" /> <path d="M17.553 16.75a7.5 7.5 0 0 0 -10.606 0" /> <path d="M18 3.804a6 6 0 0 0 -8.196 2.196l10.392 6a6 6 0 0 0 -2.196 -8.196z" /> <path d="M16.732 10c1.658 -2.87 2.225 -5.644 1.268 -6.196c-.957 -.552 -3.075 1.326 -4.732 4.196" /> <path d="M15 9l-3 5.196" /> <path d="M3 19.25a2.4 2.4 0 0 1 1 -.25a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 1 .25" /> </svg> )}const ElevatorIcon = ({ className }: { className?: string }) => { return ( <svg aria-label="Elevator" className={className} fill="none" height="24" role="img" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M0 0h24v24H0z" fill="none" stroke="none" /> <path d="M5 4m0 1a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1z" /> <path d="M10 10l2 -2l2 2" /> <path d="M10 14l2 2l2 -2" /> </svg> )}const FoodIcon = ({ className }: { className?: string }) => { return ( <svg aria-label="Food" className={className} fill="none" height="24" role="img" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M0 0h24v24H0z" fill="none" stroke="none" /> <path d="M20 20c0 -3.952 -.966 -16 -4.038 -16s-3.962 9.087 -3.962 14.756c0 -5.669 -.896 -14.756 -3.962 -14.756c-3.065 0 -4.038 12.048 -4.038 16" /> </svg> )}const MicIcon = ({ className }: { className?: string }) => { return ( <svg aria-label="Microphone" className={className} fill="none" height="24" role="img" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M0 0h24v24H0z" fill="none" stroke="none" /> <path d="M15 12.9a5 5 0 1 0 -3.902 -3.9" /> <path d="M15 12.9l-3.902 -3.899l-7.513 8.584a2 2 0 1 0 2.827 2.83l8.588 -7.515z" /> </svg> )}const ParachuteIcon = ({ className }: { className?: string }) => { return ( <svg aria-label="Parachute" className={className} fill="none" height="24" role="img" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M0 0h24v24H0z" fill="none" stroke="none" /> <path d="M22 12a10 10 0 1 0 -20 0" /> <path d="M22 12c0 -1.66 -1.46 -3 -3.25 -3c-1.8 0 -3.25 1.34 -3.25 3c0 -1.66 -1.57 -3 -3.5 -3s-3.5 1.34 -3.5 3c0 -1.66 -1.46 -3 -3.25 -3c-1.8 0 -3.25 1.34 -3.25 3" /> <path d="M2 12l10 10l-3.5 -10" /> <path d="M15.5 12l-3.5 10l10 -10" /> </svg> )}
Built for React applications with TypeScript and Next.js. Uses Framer Motion for physics-based animations that feel natural and responsive. The modal doesn't just appear—it springs into view with depth and dimension. Backdrop blur creates focus without harsh overlays. Context-based state management keeps your code clean. Perfect for shadcn/ui design systems.
Developers throw a div with position fixed and call it a modal. Maybe they add a fade transition if they're feeling generous. The problem? Users lose spatial context. The modal feels disconnected from the action that triggered it. There's no visual continuity between states. It's jarring and breaks the flow of interaction.
This component uses 3D perspective transforms that create depth and hierarchy. The spring animations follow natural physics, not linear progressions. The backdrop blur maintains visual context while focusing attention. Outside click detection and escape key handling respect user expectations. It's not just a popup—it's a smooth transition between interface states.
The compound component pattern means you get full control over content while the modal handles all the complex animation orchestration, scroll locking, and focus management automatically.
npx shadcn@latest add https://www.shadcn.io/registry/animated-modal.json
npx shadcn@latest add https://www.shadcn.io/registry/animated-modal.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/animated-modal.json
bunx shadcn@latest add https://www.shadcn.io/registry/animated-modal.json
Basic modal with title, content, and action buttons:
import { Modal, ModalBody, ModalContent, ModalFooter, ModalTrigger,} from "~/packages/components/interactive/animated-modal"export default function AnimatedModalSimpleDemo() { return ( <div className="flex items-center justify-center"> <Modal> <ModalTrigger className="bg-black dark:bg-white dark:text-black text-white"> Open Modal </ModalTrigger> <ModalBody> <ModalContent> <h4 className="text-lg md:text-2xl text-neutral-600 dark:text-neutral-100 font-bold text-center mb-4"> Welcome to Modal </h4> <p className="text-neutral-600 dark:text-neutral-400 text-center"> This is a simple modal with smooth animations and backdrop blur. Click outside or the close button to dismiss. </p> </ModalContent> <ModalFooter className="gap-4"> <button type="button" className="px-4 py-2 bg-gray-200 text-black dark:bg-black dark:border-black dark:text-white border border-gray-300 rounded-md text-sm" > Cancel </button> <button type="button" className="bg-black text-white dark:bg-white dark:text-black text-sm px-4 py-2 rounded-md border border-black" > Confirm </button> </ModalFooter> </ModalBody> </Modal> </div> )}
- 3D perspective animations with spring physics and depth effects using Framer Motion
- Backdrop blur effects maintaining visual context while focusing attention
- Context-based state with React Context for clean component architecture
- Outside click detection automatically closing modal on backdrop clicks
- Animated trigger buttons with hover effects and smooth transitions
- Scroll lock management preventing background scroll when modal is open
- Keyboard navigation with Escape key support for accessibility
- TypeScript definitions with complete interface definitions and prop types
- Responsive design with mobile-friendly layouts and adaptive sizing
- Focus management trapping focus within modal for accessibility
This free open source React component works perfectly for:
- Confirmation dialogs - User action confirmations built with Next.js
- Contact forms - Lead generation and support forms using TypeScript
- Product showcases - E-commerce product details with image galleries
- Content previews - Article previews and detailed information panels
- Settings panels - Application preferences and configuration interfaces
- Multi-step wizards - Guided processes with modal-based workflows
- Media galleries - Image and video viewers with smooth transitions
- Booking interfaces - Reservation and appointment scheduling forms
The root component providing context for all modal functionality.
| Prop | Type | Default | Description |
|---|
children | ReactNode | required | Modal trigger and body components |
defaultOpen | boolean | false | Initial open state of modal |
open | boolean | - | Controlled open state |
onOpenChange | (open: boolean) => void | - | Callback when open state changes |
Button component that opens the modal when clicked.
| Prop | Type | Default | Description |
|---|
children | ReactNode | required | Content to display inside trigger button |
className | string | - | Additional CSS classes for styling |
asChild | boolean | false | Render as child element instead of button |
...props | ButtonHTMLAttributes | - | All native button props supported |
Container for modal content with backdrop and animations.
| Prop | Type | Default | Description |
|---|
children | ReactNode | required | Modal content and footer components |
className | string | - | Additional CSS classes for modal container |
closeOnOutsideClick | boolean | true | Close modal when clicking backdrop |
showCloseButton | boolean | true | Display close button in top-right |
Main content area with padding and scroll support.
| Prop | Type | Default | Description |
|---|
children | ReactNode | required | Content to display inside modal |
className | string | - | Additional CSS classes for content area |
maxHeight | string | 80vh | Maximum height before scrolling |
Footer section for action buttons with consistent styling.
| Prop | Type | Default | Description |
|---|
children | ReactNode | required | Footer content (usually buttons) |
className | string | - | Additional CSS classes for footer |
align | 'left' | 'center' | 'right' | 'right' | Button alignment in footer |
Z-index conflicts: The modal uses a high z-index (50) by default. If you have other overlays or dropdowns, ensure proper stacking context to avoid elements appearing above the modal.
Scroll restoration issues: The component locks body scroll when open, but rapidly toggling modals might cause scroll position loss. Store scroll position if you need to preserve it across modal sessions.
Animation performance on mobile: The 3D transforms and backdrop blur can be GPU-intensive on older devices. Consider reducing animation complexity or disabling blur for better mobile performance.
Form submission in modals: Forms inside modals should prevent default submission to avoid page reloads. Handle form submission programmatically and close the modal after successful submission.
Nested modals: While technically possible, nested modals create poor UX and accessibility issues. Consider using multi-step content within a single modal instead of stacking multiple modals.
Content height changes: Dynamic content that changes height during modal lifetime might cause layout jumps. Set a consistent height or use smooth height animations for better visual continuity.
Explore other interactive components for React applications: