Modal
A dialog component that displays content in a layer above the main application. Modals can be used for confirmations, forms, detailed information, or any content that requires focused user attention.
Basic Usage
Basic Modal
import { Modal, Button } from 'fluxo-ui';
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)}
title="Modal Title"
>
<p>Modal content goes here...</p>
<div className="flex justify-end gap-2 mt-4">
<Button onClick={() => setIsOpen(false)} layout="outlined">
Cancel
</Button>
<Button onClick={() => setIsOpen(false)} variant="primary">
Confirm
</Button>
</div>
</Modal>
</>
);
}Sizes
Modal Sizes
<Modal size="sm" isOpen={isOpen} onClose={onClose} title="Small">
Small modal content
</Modal>
<Modal size="md" isOpen={isOpen} onClose={onClose} title="Medium">
Medium modal content (default)
</Modal>
<Modal size="lg" isOpen={isOpen} onClose={onClose} title="Large">
Large modal content
</Modal>
<Modal size="xl" isOpen={isOpen} onClose={onClose} title="Extra Large">
Extra large modal content
</Modal>Without Title
Modal Without Title
<Modal isOpen={isOpen} onClose={onClose}>
<h2 className="text-xl font-semibold mb-4">Custom Header</h2>
<p>Your custom content here...</p>
</Modal>Scrollable Content
Modal with Long Content
<Modal
isOpen={isOpen}
onClose={onClose}
title="Terms and Conditions"
footer={
<div className="flex justify-end gap-2">
<Button onClick={onClose} layout="outlined">Decline</Button>
<Button onClick={onClose} variant="success">Accept</Button>
</div>
}
>
{/* Content scrolls automatically when it exceeds viewport */}
<p>Long content...</p>
<p>More content...</p>
</Modal>Non-closable Modal
Modal Without Backdrop Close
<Modal
isOpen={isOpen}
onClose={onClose}
title="Important Action"
closeOnBackdrop={false}
>
<p>This modal requires explicit action.</p>
<p>Clicking outside won't close it.</p>
<div className="flex justify-end gap-2 mt-4">
<Button onClick={onClose}>Cancel</Button>
<Button onClick={handleConfirm} variant="danger">Confirm</Button>
</div>
</Modal>Nested Modals
Modal Opening Another Modal
function MyComponent() {
const [firstModal, setFirstModal] = useState(false);
const [secondModal, setSecondModal] = useState(false);
return (
<>
<Button onClick={() => setFirstModal(true)}>Open First Modal</Button>
<Modal isOpen={firstModal} onClose={() => setFirstModal(false)} title="First Modal">
<p>First modal content</p>
<Button onClick={() => setSecondModal(true)}>Open Second Modal</Button>
</Modal>
<Modal isOpen={secondModal} onClose={() => setSecondModal(false)} title="Second Modal">
<p>Nested modal content</p>
</Modal>
</>
);
}Custom Header and Footer
Modal with Custom Layout
<Modal isOpen={isOpen} onClose={onClose}>
{/* Custom Header */}
<div className="flex items-center justify-between pb-4 border-b">
<div>
<h2 className="text-2xl font-bold">Custom Header</h2>
<p className="text-sm text-gray-500">With subtitle</p>
</div>
<span className="px-3 py-1 bg-green-100 rounded-full">Active</span>
</div>
{/* Content */}
<div className="py-4">
<p>Your modal content here...</p>
</div>
{/* Custom Footer */}
<div className="flex justify-between pt-4 border-t">
<Button layout="plain">Skip</Button>
<div className="flex gap-2">
<Button onClick={onClose} layout="outlined">Cancel</Button>
<Button onClick={onSave} variant="success">Save</Button>
</div>
</div>
</Modal>Form Example
Modal with Form
Modals work great for forms. Here's an example of how to structure a form inside a modal:
function FormModal() {
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState({ name: '', email: '' });
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Form submitted:', formData);
setIsOpen(false);
};
return (
<>
<Button onClick={() => setIsOpen(true)}>Add User</Button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Add New User">
<form onSubmit={handleSubmit}>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">Name</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Email</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
</div>
<div className="flex justify-end gap-2 mt-6">
<Button type="button" onClick={() => setIsOpen(false)} layout="outlined">Cancel</Button>
<Button type="submit" variant="primary">Create User</Button>
</div>
</form>
</Modal>
</>
);
}Import
import { Modal } from 'fluxo-ui';Props
isOpenreqbooleanControls whether the modal is visible
isOpenreqbooleanControls whether the modal is visible
onClosereq() => voidCallback function triggered when modal should close
onClosereq() => voidCallback function triggered when modal should close
titlestringModal title displayed in the header. If not provided, close button appears in top-right corner
titlestringModal title displayed in the header. If not provided, close button appears in top-right corner
childrenreqReact.ReactNodeModal content (scrollable when it overflows)
childrenreqReact.ReactNodeModal content (scrollable when it overflows)
footerReact.ReactNodeFooter content rendered below the scrollable area. Always visible on screen regardless of content height
footerReact.ReactNodeFooter content rendered below the scrollable area. Always visible on screen regardless of content height
size'sm' | 'md' | 'lg' | 'xl' | 'fullScreen'"'md'"Modal width size (sm: 28rem, md: 32rem, lg: 42rem, xl: 56rem, fullScreen: full viewport)
size'sm' | 'md' | 'lg' | 'xl' | 'fullScreen'"'md'"Modal width size (sm: 28rem, md: 32rem, lg: 42rem, xl: 56rem, fullScreen: full viewport)
closeOnBackdropboolean"true"Whether clicking the backdrop (overlay) closes the modal. Drag-out gestures starting inside the modal are ignored.
closeOnBackdropboolean"true"Whether clicking the backdrop (overlay) closes the modal. Drag-out gestures starting inside the modal are ignored.
initialFocusRefObject<HTMLElement>Element to receive focus when the modal opens. Defaults to the first focusable element.
initialFocusRefObject<HTMLElement>Element to receive focus when the modal opens. Defaults to the first focusable element.
ariaLabelstringAccessible label when no title is provided
ariaLabelstringAccessible label when no title is provided
Features
Portal Rendering
Rendered via React portals so modals always appear above all other content
Multiple Sizes
Four preset widths — sm, md, lg, xl — to fit any content need
Scrollable Body
Content area scrolls independently while the footer stays fixed on screen
Sticky Footer
Optional footer prop always visible at the bottom, even with long content
Keyboard Support
Escape key closes the modal; focus is trapped inside while open
Backdrop Control
closeOnBackdrop prop disables click-outside for critical confirmations
Nested Modals
Multiple modals can be stacked with independent close handlers
Scroll Lock
Body scrolling is locked while the modal is open to prevent background movement
Smooth Animations
Scale and opacity transitions on open and close for a polished feel
Accessibility
ARIA dialog role, focus management, and screen reader labels built in