Skip to content

Modal ​

Display content in an overlay dialog with backdrop, animation, and flexible sizing. Ideal for confirmations, forms, and contextual content. Fully reactive and accessible, following Preline design patterns.

Import ​

tsx
import { Modal } from '@odyssee-software/components';

Basic Usage ​

tsx
const isOpen = Pulse.signal(false);

<Button onClick={() => isOpen(true)}>Open Modal</Button>

<Modal
  isOpen={isOpen}
  title="Modal Title"
  onClose={() => isOpen(false)}
>
  <p>This is the modal content.</p>
</Modal>

Variants ​

tsx
<Modal
  isOpen={isOpen}
  title="Confirm Action"
  onClose={() => isOpen(false)}
  footer={
    <>
      <Button variant="outline" onClick={() => isOpen(false)}>Cancel</Button>
      <Button variant="solid" color="primary" onClick={handleConfirm}>Confirm</Button>
    </>
  }
>
  <p>Are you sure you want to proceed?</p>
</Modal>

Sizes ​

tsx
<Modal isOpen={isOpen} title="Small Modal" size="sm" onClose={() => isOpen(false)}>
  <p>Compact content</p>
</Modal>

<Modal isOpen={isOpen} title="Large Modal" size="lg" onClose={() => isOpen(false)}>
  <p>Spacious content</p>
</Modal>

Vertically Centered ​

tsx
<Modal isOpen={isOpen} title="Centered" centered={true} onClose={() => isOpen(false)}>
  <p>This modal is vertically centered.</p>
</Modal>

Static Backdrop ​

tsx
<Modal isOpen={isOpen} title="Static" staticBackdrop={true} onClose={() => isOpen(false)}>
  <p>Cannot close by clicking outside or pressing Escape.</p>
</Modal>

Fullscreen ​

tsx
<Modal isOpen={isOpen} title="Fullscreen" fullscreen={true} onClose={() => isOpen(false)}>
  <p>This modal takes up the entire viewport.</p>
</Modal>

Animation Variants ​

tsx
<Modal isOpen={isOpen} title="Scale Animation" animation="scale" onClose={() => isOpen(false)}>
  <p>Appears with a scale animation.</p>
</Modal>

Props ​

PropTypeDefaultDescription
isOpenboolean | Signal<boolean>falseControls modal visibility (reactive or static)
titlestring | HTMLElement—Modal header title
childrenstring | HTMLElement | HTMLElement[]—Modal body content
footerHTMLElement | HTMLElement[]—Custom footer (actions, buttons, etc.)
size"sm" | "md" | "lg" | "xl" | "2xl""md"Modal width
centeredbooleanfalseVertically center the modal
staticBackdropbooleanfalsePrevent closing by clicking outside
fullscreenbooleanfalseDisplay modal fullscreen
showCloseButtonbooleantrueShow close button in header
closeOnEscapebooleantrueAllow closing with Escape key
animation"scale" | "slideDown" | "slideUp" | "fade""slideDown"Animation style for modal appearance/disappearance
onClose() => void—Callback when modal requests to close
classNamestring—Additional CSS classes
idstringautoHTML id attribute (auto-generated if not provided)
stylestring | CSSStyleDeclaration—Inline styles

Accessibility ​

  • Uses role="dialog" and appropriate ARIA attributes (aria-labelledby, focus trap)
  • Can be closed with Escape (unless disabled)
  • Focus is automatically managed when opening/closing
  • Close button is accessible (aria-label="Close")
  • Body scroll is disabled while modal is open

Best Practices ​

  • Use a Pulse signal for isOpen for reactivity.
  • Place main actions in the footer.
  • Use staticBackdrop for critical dialogs that must not be dismissed accidentally.
  • Choose the modal size appropriate for your content.
  • Always provide a clear title for accessibility.

Minimal Example ​

tsx
const isOpen = Pulse.signal(false);

<Button onClick={() => isOpen(true)}>Open Modal</Button>

<Modal
  isOpen={isOpen}
  title="Title"
  onClose={() => isOpen(false)}
>
  <p>Modal content</p>
</Modal>

Version: 1.0.0
Last Updated: June 2024

Released under the MIT License.