Bottom Modal
tripled/bottom-modalFreeComponent
Cute bottom-centered modal with smooth slide-up animation and glassmorphism design
Live preview
live · rendered by the registry
Rendered by tripled on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.tripled.work/r/bottom-modal.jsonSource
1"use client";23import { Button } from "@/components/ui/button";4import { AnimatePresence, motion, MotionConfig } from "framer-motion";5import { Sparkles, X } from "lucide-react";6import { useEffect, useRef, useState } from "react";78export function BottomModal() {9 const [isModalOpen, setIsModalOpen] = useState(false);10 const modalRef = useRef<HTMLDivElement>(null);11 const triggerRef = useRef<HTMLButtonElement>(null);1213 // Focus management: trap Tab inside the dialog, close on Escape,14 // lock body scroll, and restore focus to the trigger on close.15 useEffect(() => {16 if (!isModalOpen) return;1718 const focusables = modalRef.current?.querySelectorAll<HTMLElement>(19 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'20 );21 focusables?.[0]?.focus();2223 const handleKeyDown = (e: KeyboardEvent) => {24 if (e.key === "Escape") {25 setIsModalOpen(false);26 return;27 }28 if (e.key !== "Tab" || !focusables || focusables.length === 0) return;29 const first = focusables[0];30 const last = focusables[focusables.length - 1];31 if (e.shiftKey && document.activeElement === first) {32 e.preventDefault();33 last.focus();34 } else if (!e.shiftKey && document.activeElement === last) {35 e.preventDefault();36 first.focus();37 }38 };3940 document.addEventListener("keydown", handleKeyDown);41 const previousOverflow = document.body.style.overflow;42 document.body.style.overflow = "hidden";4344 return () => {45 document.removeEventListener("keydown", handleKeyDown);46 document.body.style.overflow = previousOverflow;47 triggerRef.current?.focus();48 };49 }, [isModalOpen]);5051 return (52 <MotionConfig reducedMotion="user">53 <div className="text-center space-y-8 w-full mx-auto">54 <motion.div55 initial={{ opacity: 0, y: 20 }}56 animate={{ opacity: 1, y: 0 }}57 transition={{ duration: 0.4, delay: 0.1 }}58 >59 <Button60 ref={triggerRef}61 onClick={() => setIsModalOpen(true)}62 size="lg"63 aria-haspopup="dialog"64 aria-expanded={isModalOpen}65 className="gap-2 w-full rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 shadow-lg hover:shadow-xl transition-shadow duration-300"66 >67 <Sparkles className="h-5 w-5" aria-hidden="true" />68 Open Bottom Modal69 </Button>70 </motion.div>7172// … truncated
What it pulls in
npm packages
Files it writes
More from tripled
All 282 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| AI Chat Interfaceai-chat-interface-shadcnui | tripled | Components | Free | 2 deps | |
| AI Glow Inputai-glow-input-shadcnui | tripled | Components | Free | 2 deps | |
| AI Loading Skeletonai-loading-skeleton-shadcnui | tripled | Components | Free | 2 deps | |
| AI Response Typing Streamai-response-typing-shadcnui | tripled | Components | Free | 2 deps | |
| AI Unlock Animationai-unlock-animation-shadcnui | tripled | Components | Free | 2 deps | |
| Animated Accordionanimated-accordion-shadcnui | tripled | Components | Free | 2 deps | |
| Animated Card Stackanimated-card-stack-baseui | tripled | Components | Free | 2 deps | |
| Animated Card Stackanimated-card-stack-shadcnui | tripled | Components | Free | 2 deps |
