Modal dialog component for overlays, popups, and dialogs with customizable content and behavior.
Location: src/components/ui/Modal.tsx
Overview
The Modal component is a flexible dialog component for displaying content in an overlay. It supports custom titles, footer content, click-outside-to-close behavior, and keyboard navigation (ESC to close). Built on DaisyUI's modal classes for consistent styling.
Quick Customization
This component is highly customizable:
- Content - Any React content (titles, forms, images, etc.)
- Title - Optional title prop or custom title element
- Footer - Optional footer with buttons or actions
- Close behavior - Toggle click-outside-to-close
- Keyboard support - ESC key automatically closes modal
- Styling - Additional CSS classes for custom styling
Usage
import Modal from "@/components/ui/Modal";
import { useState } from "react";
function MyComponent() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
<p>Modal content here</p>
</Modal>
</>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | Required | Whether modal is open |
onClose | () => void | Required | Callback when modal should close |
title | string | undefined | Optional modal title |
children | ReactNode | Required | Modal content |
footer | ReactNode | undefined | Optional footer content |
closeOnOutsideClick | boolean | true | Whether to close on outside click |
className | string | "" | Additional CSS classes for modal box |
Examples
Basic Modal
import Modal from "@/components/ui/Modal";
import { useState } from "react";
function BasicModal() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
<p>This is a basic modal with no title or footer.</p>
</Modal>
</>
);
}
Modal with Title
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Confirm Action"
>
<p>Are you sure you want to proceed?</p>
</Modal>
Modal with Footer
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Delete Item"
footer={
<div className="flex gap-2 justify-end">
<button className="btn btn-ghost" onClick={() => setIsOpen(false)}>
Cancel
</button>
<button className="btn btn-error" onClick={handleDelete}>
Delete
</button>
</div>
}
>
<p>This action cannot be undone.</p>
</Modal>
Modal with Form
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Edit Profile"
footer={
<div className="flex gap-2 justify-end">
<button className="btn btn-ghost" onClick={() => setIsOpen(false)}>
Cancel
</button>
<button className="btn btn-primary" onClick={handleSave}>
Save
</button>
</div>
}
>
<form className="space-y-4">
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
</form>
</Modal>
Modal without Click-Outside Close
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
closeOnOutsideClick={false}
title="Important Notice"
>
<p>This modal cannot be closed by clicking outside.</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</Modal>
Custom Styled Modal
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
className="max-w-2xl" // Custom max-width
title="Custom Modal"
>
<div className="space-y-4">
<p>Custom styled modal content.</p>
</div>
</Modal>
Component Structure
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: ReactNode;
footer?: ReactNode;
closeOnOutsideClick?: boolean;
className?: string;
}
Behavior
Opening and Closing
- Open: Set
isOpen={true}to show the modal - Close: Set
isOpen={false}or callonClose()to hide - ESC key: Automatically closes modal when pressed
- Click outside: Closes modal if
closeOnOutsideClick={true}(default)
Accessibility
- Focus trap: Modal should trap focus (can be added with a library)
- ARIA labels: Title prop adds semantic structure
- Keyboard navigation: ESC key support included
Styling Notes
- Base classes: Uses DaisyUI
modalandmodal-boxclasses - Backdrop: Dark overlay with blur effect (
bg-black/50 backdrop-blur-sm) - Animation: Smooth transitions for open/close states
- Max width: Default max-width, customizable with
className - Responsive: Adapts to mobile screen sizes
Best Practices
- State management - Use
useStateor state management library to controlisOpen - Close handlers - Always provide an
onClosecallback - Form modals - Include footer with action buttons (Save/Cancel)
- Large content - Add
max-w-*classes for large modals - Avoid nesting - Don't nest modals inside modals (bad UX)
Tips
- Simple modals: Use
titleprop for quick titles - Complex modals: Omit
titleand add custom header JSX inchildren - Actions: Always provide clear action buttons in
footer - Mobile: Modals work well on mobile with proper max-width
- Loading states: Show loading spinner in modal while processing
Common Use Cases
Confirmation Dialogs
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Confirm Delete"
footer={
<>
<button onClick={() => setIsOpen(false)}>Cancel</button>
<button onClick={handleConfirm}>Delete</button>
</>
}
>
Are you sure you want to delete this item?
</Modal>
Form Modals
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Create New Item"
footer={
<>
<button onClick={() => setIsOpen(false)}>Cancel</button>
<button onClick={handleSubmit}>Create</button>
</>
}
>
<form>{/* Form fields */}</form>
</Modal>
Information Modals
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Information"
>
<p>Here's some important information...</p>
</Modal>
Learn More
- Button Component - Used in modal footers
- Input Component - Used in modal forms
- UI Components Overview - All available UI components