Fluxo UIFluxo UIv0.4.1

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

Basic Example
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

isOpenreq
boolean

Controls whether the modal is visible

onClosereq
() => void

Callback function triggered when modal should close

title
string

Modal title displayed in the header. If not provided, close button appears in top-right corner

childrenreq
React.ReactNode

Modal content (scrollable when it overflows)

footer
React.ReactNode

Footer 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)

closeOnBackdrop
boolean"true"

Whether clicking the backdrop (overlay) closes the modal. Drag-out gestures starting inside the modal are ignored.

initialFocus
RefObject<HTMLElement>

Element to receive focus when the modal opens. Defaults to the first focusable element.

ariaLabel
string

Accessible 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