--- title: Liquid Glass Dialog description: A realistic 3D liquid glass modal dialog component featuring multi-stage crystal glass panel depth, top curved gloss glare, bottom prism refraction, backdrop blur, modular sub-components (Header, Title, Description, Body, Footer), 5 size options, and smooth Framer Motion spring physics. --- ## Installation CLI Manual ```bash npx lightswind@latest add liquid-glass-dialog ``` Copy and paste the following code into your project. ```tsx "use client"; import React, { useEffect, useCallback, useId } from "react"; import { createPortal } from "react-dom"; import { cn } from "@/components/lib/utils"; import { motion, AnimatePresence } from "framer-motion"; import { X } from "lucide-react"; /* ─── Types ─────────────────────────────────────────────────────── */ export type LiquidGlassDialogSize = "sm" | "md" | "lg" | "xl" | "full"; export interface LiquidGlassDialogProps { /** Controlled open state */ open: boolean; onOpenChange: (open: boolean) => void; /** Dialog title */ title?: string; /** Optional subtitle below the title */ description?: string; /** Dialog size */ size?: LiquidGlassDialogSize; /** Show close button (default: true) */ showClose?: boolean; /** Close when clicking the backdrop (default: true) */ closeOnBackdrop?: boolean; /** Extra classes for the panel */ className?: string; children?: React.ReactNode; } /* ─── Size Map ──────────────────────────────────────────────────── */ const SIZE_MAP: Record = { sm: "max-w-sm", md: "max-w-md", lg: "max-w-lg", xl: "max-w-xl", full: "max-w-3xl", }; /* ─── Sub-components ─────────────────────────────────────────────── */ export interface LiquidGlassDialogHeaderProps extends React.HTMLAttributes { children: React.ReactNode; } export const LiquidGlassDialogHeader: React.FC = ({ className, children, ...props }) => ( {children} ); LiquidGlassDialogHeader.displayName = "LiquidGlassDialogHeader"; export interface LiquidGlassDialogTitleProps extends React.HTMLAttributes { children: React.ReactNode; } export const LiquidGlassDialogTitle: React.FC = ({ className, children, ...props }) => ( {children} ); LiquidGlassDialogTitle.displayName = "LiquidGlassDialogTitle"; export interface LiquidGlassDialogDescriptionProps extends React.HTMLAttributes { children: React.ReactNode; } export const LiquidGlassDialogDescription: React.FC = ({ className, children, ...props }) => ( {children} ); LiquidGlassDialogDescription.displayName = "LiquidGlassDialogDescription"; export interface LiquidGlassDialogBodyProps extends React.HTMLAttributes { children: React.ReactNode; } export const LiquidGlassDialogBody: React.FC = ({ className, children, ...props }) => ( {children} ); LiquidGlassDialogBody.displayName = "LiquidGlassDialogBody"; export interface LiquidGlassDialogFooterProps extends React.HTMLAttributes { children: React.ReactNode; } export const LiquidGlassDialogFooter: React.FC = ({ className, children, ...props }) => ( {children} ); LiquidGlassDialogFooter.displayName = "LiquidGlassDialogFooter"; /* ─── Main Dialog ────────────────────────────────────────────────── */ export const LiquidGlassDialog: React.FC = ({ open, onOpenChange, title, description, size = "md", showClose = true, closeOnBackdrop = true, className, children, }) => { const titleId = useId(); const descId = useId(); // Escape key closes dialog const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "Escape") onOpenChange(false); }, [onOpenChange] ); useEffect(() => { if (open) { document.addEventListener("keydown", handleKeyDown); document.body.style.overflow = "hidden"; } return () => { document.removeEventListener("keydown", handleKeyDown); document.body.style.overflow = ""; }; }, [open, handleKeyDown]); if (typeof window === "undefined") return null; return createPortal( {open && ( <> {/* ── Frosted Backdrop ─────────────────────────────── */} onOpenChange(false) : undefined} className="fixed inset-0 z-[99998] backdrop-blur-md" style={{ background: "linear-gradient(135deg, rgba(15,23,42,0.65) 0%, rgba(30,0,60,0.55) 100%)", }} aria-hidden="true" /> {/* ── Dialog Panel ─────────────────────────────────── */} e.stopPropagation()} className="fixed z-[99999] inset-0 flex items-center justify-center p-4 pointer-events-none" > {/* Top gloss glare — same as LiquidGlassAlertDialog */} {/* Bottom prism refraction */} {/* ── Close Button ───────────────────────────────── */} {showClose && ( onOpenChange(false)} type="button" aria-label="Close dialog" className={cn( "absolute top-4 right-4 z-20 w-8 h-8 rounded-full flex items-center justify-center transition-all duration-200 cursor-pointer", "bg-slate-900/10 dark:bg-black/40 hover:bg-slate-900/20 dark:hover:bg-black/60", "border border-slate-900/15 dark:border-white/30", "shadow-[inset_0_1.5px_1px_rgba(255,255,255,0.8)] dark:shadow-[inset_0_1.5px_1px_rgba(255,255,255,0.3)]", "text-slate-800 dark:text-white" )} > )} {/* ── Content ────────────────────────────────────── */} {/* Header (title + description) */} {(title || description) && ( {title && ( {title} )} {description && ( {description} )} )} {/* Slot content */} {children} > )} , document.body ); }; LiquidGlassDialog.displayName = "LiquidGlassDialog"; export default LiquidGlassDialog; ``` ## Usage ```tsx import LiquidGlassDialog, { LiquidGlassDialogHeader, LiquidGlassDialogTitle, LiquidGlassDialogDescription, LiquidGlassDialogBody, LiquidGlassDialogFooter } from "@/components/lightswind/liquid-glass-dialog" ``` ```tsx import { useState } from "react"; import LiquidGlassDialog, { LiquidGlassDialogFooter } from "@/components/lightswind/liquid-glass-dialog"; import { LiquidGlassButton } from "@/components/lightswind/liquid-glass-button"; export function Demo() { const [open, setOpen] = useState(false); return ( <> setOpen(true)}> Open Dialog Dialog body content goes here... setOpen(false)}> Cancel setOpen(false)}> Save > ); } ```
{children}
{description}
Dialog body content goes here...