logo

Nurav UI

Community
Components

Modal

A premium modal component with spring animations, backdrop blurring, and integrated sound effects.Copy Markdown

Preview

The Modal component provides a focused overlay for important content or interactions.

Loading...

Variants

Default

The standard modal is suitable for complex forms, large content areas, or detailed information.

Alert

The alert variant is a compact, centered dialog with deep backdrop blur and rounded corners, specifically designed for quick confirmations or destructive actions.

<Modal variant="alert" ... />

Features

  • Backdrop Blur: Uses backdrop-blur for a modern, glassmorphic appearance.
  • Spring Animations: Framer Motion powered entry and exit transitions.
  • Audible Feedback: Integrated useSound hook for premium clicking sounds.
  • Accessible: Handles Escape key to close and click-outside behavior.
  • Responsive: Adapts gracefully to mobile devices with centered layouts.

Installation

Automatic (CLI)

Before using the @nurav-ui alias, add this to your components.json:

components.json
{
  "registries": {
    "@nurav-ui": "https://nurav-ui.vercel.app/r"
  }
}
Terminal
npx shadcn@latest add @nurav-ui/modal

Alternatively, install via direct URL:

Terminal
npx shadcn@latest add https://nurav-ui.vercel.app/r/modal.json

Manual Setup

Install Dependencies

Terminal
npm install motion lucide-react clsx tailwind-merge

Create Component

Copy the code below into your project (e.g., components/nurav-ui/Modal.tsx):

Modal.tsx
"use client";

import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { motion, AnimatePresence } from "motion/react";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { useSound } from "@/registry/hooks/use-sound";

interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  title?: string;
  description?: string;
  children: React.ReactNode;
  className?: string;
  showCloseButton?: boolean;
  variant?: "default" | "alert";
  containerRef?: React.RefObject<HTMLElement | null>;
}

export function Modal({
  isOpen,
  onClose,
  title,
  description,
  children,
  className,
  showCloseButton = true,
  variant = "default",
  containerRef,
}: ModalProps) {
  const { clickOn, clickOff } = useSound();
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  useEffect(() => {
    if (isOpen) clickOn();
  }, [isOpen, clickOn]);

  const handleClose = () => {
    clickOff();
    onClose();
  };

  useEffect(() => {
    const handleEsc = (e: KeyboardEvent) => {
      if (e.key === "Escape") handleClose();
    };

    if (isOpen) {
      window.addEventListener("keydown", handleEsc);
      document.body.style.overflow = "hidden";
    } else {
      document.body.style.overflow = "";
    }

    return () => {
      window.removeEventListener("keydown", handleEsc);
      document.body.style.overflow = "";
    };
  }, [isOpen]);

  if (!mounted) return null;

  return createPortal(
    <AnimatePresence>
      {isOpen && (
        <div className="fixed inset-0 z-9999 flex items-center justify-center p-4">
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={handleClose}
            className="absolute inset-0 bg-black/40 backdrop-blur-md"
          />

          <motion.div
            initial={{ opacity: 0, scale: 0.9, y: 20 }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            exit={{ opacity: 0, scale: 0.9, y: 20 }}
            transition={{ type: "spring", damping: 25, stiffness: 350 }}
            className={cn(
              "relative w-full overflow-hidden transition-all duration-300",
              variant === "alert"
                ? "max-w-[400px] rounded-[40px] bg-[#1c1c1e]/80 p-10 text-center backdrop-blur-3xl border border-white/10 shadow-[0_24px_50px_-12px_rgba(0,0,0,0.5)]"
                : "max-w-lg rounded-2xl bg-white p-6 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 shadow-2xl",
              className,
            )}
          >
            {showCloseButton && variant !== "alert" && (
              <button
                onClick={handleClose}
                className="absolute right-4 top-4 rounded-full p-1 text-neutral-500 transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-800"
              >
                <X size={18} />
              </button>
            )}

            {(title || description) && (
              <div
                className={cn(
                  "space-y-3",
                  variant === "alert" ? "mb-10" : "mb-6",
                )}
              >
                {title && (
                  <h2
                    className={cn(
                      "font-semibold tracking-tight",
                      variant === "alert"
                        ? "text-[22px] leading-tight text-white/90 px-2"
                        : "text-xl text-neutral-900 dark:text-neutral-100",
                    )}
                  >
                    {title}
                  </h2>
                )}
                {description && (
                  <p
                    className={cn(
                      "text-sm",
                      variant === "alert"
                        ? "text-neutral-400 font-medium"
                        : "text-neutral-500 dark:text-neutral-400",
                    )}
                  >
                    {description}
                  </p>
                )}
              </div>
            )}

            <div className="relative">{children}</div>
          </motion.div>
        </div>
      )}
    </AnimatePresence>,
    containerRef?.current || document.body,
  );
}

Props

PropTypeDefaultDescription
isOpenrequired
booleanfalseControls whether the modal is visible.
onCloserequired
() => voidCallback function when the modal is closed.
title
stringOptional title displayed at the top.
description
stringOptional subtle description text.
variant
'default' | 'alert''default'Visual variant of the modal.
showCloseButton
booleantrueWhether to show the 'X' button in the corner.
containerRef
React.RefObjectdocument.bodyOptional container to mount the modal into.
className
stringCustom styles for the modal container.

Built by Varun

Last Updated:April 14, 2026

On this page