Build websites 10x faster with HextaUI Blocks — Learn more
UI/UI

Modal

A simple and accessible modal dialog component with smooth animations.

<Modal>
  <ModalTrigger asChild>
    <Button>Open Modal</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Edit Profile</ModalTitle>
      <ModalDescription asChild>
        <span>
          Make changes to your profile here. Click save when you're done.
        </span>
      </ModalDescription>
    </ModalHeader>
    <div className="mt-6 mb-6">
      <div className="space-y-4">
        <div>
          <Label className="text-sm font-medium">Name</Label>
          <Input className="w-full mt-1" placeholder="Enter your name" />
        </div>
        <div>
          <Label className="text-sm font-medium">Email</Label>
          <Input className="w-full mt-1" placeholder="Enter your email" />
        </div>
      </div>
    </div>
    <ModalFooter>
      <ModalClose asChild>
        <Button variant="outline">Cancel</Button>
      </ModalClose>
      <Button>Save changes</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

Installation

Install following dependencies:

npm install @radix-ui/react-dialog motion lucide-react
pnpm add @radix-ui/react-dialog motion lucide-react
yarn add @radix-ui/react-dialog motion lucide-react
bun add @radix-ui/react-dialog motion lucide-react

Copy and paste the following code into your project.

components/ui/modal.tsx
"use client";

import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { motion } from "motion/react";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";

const Modal = DialogPrimitive.Root;

const ModalTrigger = DialogPrimitive.Trigger;

const ModalPortal = DialogPrimitive.Portal;

const ModalClose = DialogPrimitive.Close;

const ModalOverlay = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Overlay
    ref={ref}
    className={cn("fixed inset-0 z-50 bg-black/50 backdrop-blur-sm", className)}
    {...props}
  />
));
ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;

const ModalContent = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
  <ModalPortal>
    <ModalOverlay />
    <DialogPrimitive.Content asChild ref={ref} {...props}>
      <motion.div
        initial={{ opacity: 0, scale: 0.95 }}
        animate={{ opacity: 1, scale: 1 }}
        exit={{ opacity: 0, scale: 0.95 }}
        transition={{ duration: 0.2 }}
        className={cn(
          "fixed left-[50%] top-[50%] z-50 w-[95%] max-w-lg translate-x-[-50%] translate-y-[-50%] rounded-2xl bg-[hsl(var(--hu-background))] border border-[hsl(var(--hu-border))] p-6 shadow-lg",
          className,
        )}
      >
        {children}
        <ModalClose className="absolute right-4 top-4 rounded-lg p-1.5 mx-2 text-[hsl(var(--hu-muted-foreground))] hover:text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--hu-ring))] transition-colors">
          <X size={16} />
          <span className="sr-only">Close</span>
        </ModalClose>
      </motion.div>
    </DialogPrimitive.Content>
  </ModalPortal>
));
ModalContent.displayName = DialogPrimitive.Content.displayName;

const ModalHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col space-y-2 text-center sm:text-left",
      className,
    )}
    {...props}
  />
);
ModalHeader.displayName = "ModalHeader";

const ModalFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 gap-2",
      className,
    )}
    {...props}
  />
);
ModalFooter.displayName = "ModalFooter";

const ModalTitle = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Title
    ref={ref}
    className={cn(
      "text-lg font-semibold leading-none tracking-tight text-[hsl(var(--hu-foreground))]",
      className,
    )}
    {...props}
  />
));
ModalTitle.displayName = DialogPrimitive.Title.displayName;

const ModalDescription = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Description
    ref={ref}
    className={cn("text-sm text-[hsl(var(--hu-muted-foreground))]", className)}
    {...props}
  />
));
ModalDescription.displayName = DialogPrimitive.Description.displayName;

export {
  Modal,
  ModalPortal,
  ModalOverlay,
  ModalClose,
  ModalTrigger,
  ModalContent,
  ModalHeader,
  ModalFooter,
  ModalTitle,
  ModalDescription,
};
npx hextaui@latest@latest add modal
pnpm dlx hextaui@latest@latest add modal
yarn dlx hextaui@latest@latest add modal
bun x hextaui@latest@latest add modal

Examples

Basic Form Modal

<Modal>
  <ModalTrigger asChild>
    <Button>Edit Profile</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Edit Profile</ModalTitle>
      <ModalDescription asChild>
        <span>
          Make changes to your profile here. Click save when you're done.
        </span>
      </ModalDescription>
    </ModalHeader>
    <div className="mt-6 mb-6">
      <div className="space-y-4">
        <div>
          <Label className="text-sm font-medium">Name</Label>
          <Input className="w-full mt-1" placeholder="Enter your name" />
        </div>
        <div>
          <Label className="text-sm font-medium">Email</Label>
          <Input className="w-full mt-1" placeholder="Enter your email" />
        </div>
      </div>
    </div>
    <ModalFooter>
      <ModalClose asChild>
        <Button variant="outline">Cancel</Button>
      </ModalClose>
      <Button>Save changes</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

Confirmation Dialog

<Modal>
  <ModalTrigger asChild>
    <Button variant="destructive">Delete Account</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Are you absolutely sure?</ModalTitle>
      <ModalDescription asChild>
        <span>
          This action cannot be undone. This will permanently delete your
          account and remove your data from our servers.
        </span>
      </ModalDescription>
    </ModalHeader>
    <ModalFooter className="mt-6">
      <ModalClose asChild>
        <Button variant="outline">Cancel</Button>
      </ModalClose>
      <Button variant="destructive">Delete Account</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

Custom Content

<Modal>
  <ModalTrigger asChild>
    <Button variant="outline">View Details</Button>
  </ModalTrigger>
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Project Details</ModalTitle>
      <ModalDescription asChild>
        <span>Review the project information below.</span>
      </ModalDescription>
    </ModalHeader>
    <div className="mt-6 mb-6">
      <div className="space-y-3">
        <div className="flex justify-between">
          <span className="text-sm font-medium">Status:</span>
          <span className="text-sm text-green-600">Active</span>
        </div>
        <div className="flex justify-between">
          <span className="text-sm font-medium">Created:</span>
          <span className="text-sm text-[hsl(var(--hu-muted-foreground))]">
            Jan 20, 2024
          </span>
        </div>
        <div className="flex justify-between">
          <span className="text-sm font-medium">Members:</span>
          <span className="text-sm text-[hsl(var(--hu-muted-foreground))]">
            5 people
          </span>
        </div>
      </div>
    </div>
    <ModalFooter>
      <ModalClose asChild>
        <Button variant="outline">Close</Button>
      </ModalClose>
    </ModalFooter>
  </ModalContent>
</Modal>

Props

The root component is based on Radix UI Dialog Root and accepts all its props.

PropTypeDefault
onOpenChange?
(open: boolean) => void
undefined
defaultOpen?
boolean
false
open?
boolean
false

ModalTrigger

PropTypeDefault
asChild?
boolean
false

ModalContent

PropTypeDefault
children?
ReactNode
undefined
className?
string
undefined

ModalHeader

PropTypeDefault
children?
ReactNode
undefined
className?
string
undefined

ModalTitle

PropTypeDefault
children?
ReactNode
undefined
className?
string
undefined

ModalDescription

PropTypeDefault
children?
ReactNode
undefined
className?
string
undefined

ModalFooter

PropTypeDefault
children?
ReactNode
undefined
className?
string
undefined

ModalClose

PropTypeDefault
asChild?
boolean
false
Edit on GitHub

Last updated on