Skip to main content

Modal

Modal is a dialog overlay component for focused interactions that require user attention.

Open in Storybook

Import​

import { Modal, ModalHeader, ModalBody, ModalFooter } from "@tosui/react";

Basic usage​

Modal is a controlled component that requires isOpen and onClose props.

Sizes​

<VStack gap={2} align="start">
<Button onClick={() => openModal("sm")}>Small (400px)</Button>
<Button onClick={() => openModal("md")}>Medium (500px)</Button>
<Button onClick={() => openModal("lg")}>Large (700px)</Button>
<Button onClick={() => openModal("xl")}>Extra Large (900px)</Button>
<Button onClick={() => openModal("full")}>Full Screen</Button>
</VStack>

<Modal
isOpen={isOpen}
onClose={onClose}
size={size}
aria-label={`${size} modal`}
>
<ModalBody>Content for {size} modal</ModalBody>
</Modal>

Size dimensions​

SizeWidth
sm400px
md500px
lg700px
xl900px
full100%

Close behaviors​

Control how the modal can be closed.

// Disable close on overlay click
<Modal
isOpen={isOpen}
onClose={onClose}
closeOnOverlayClick={false}
aria-label="Example modal"
>
...
</Modal>

// Disable close on Escape key
<Modal
isOpen={isOpen}
onClose={onClose}
closeOnEsc={false}
aria-label="Example modal"
>
...
</Modal>

Common patterns​

Confirmation dialog​

<Modal isOpen={isOpen} onClose={onClose} size="sm" aria-label="Delete item">
<ModalHeader>
<Heading size="lg">Delete Item?</Heading>
</ModalHeader>
<ModalBody>
<Text>This action cannot be undone. Are you sure you want to delete this item?</Text>
</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={onClose}>Cancel</Button>
<Button colorScheme="error" onClick={handleDelete}>Delete</Button>
</ModalFooter>
</Modal>

Form modal​

<Modal isOpen={isOpen} onClose={onClose} aria-label="Edit profile">
<ModalHeader>
<Heading size="lg">Edit Profile</Heading>
</ModalHeader>
<ModalBody>
<VStack gap={4}>
<FormField label="Name">
<Input value={name} onChange={(e) => setName(e.target.value)} />
</FormField>
<FormField label="Email">
<Input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</FormField>
</VStack>
</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={onClose}>Cancel</Button>
<Button onClick={handleSave}>Save Changes</Button>
</ModalFooter>
</Modal>

Long content​

Modal scrolls internally when content exceeds viewport height.

<Modal isOpen={isOpen} onClose={onClose} aria-label="Terms of service">
<ModalHeader>
<Heading size="lg">Terms of Service</Heading>
</ModalHeader>
<ModalBody>
{/* Long scrollable content */}
<Text>Very long content goes here...</Text>
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>I Accept</Button>
</ModalFooter>
</Modal>

Props reference​

PropTypeDefaultDescription
isOpenboolean-Whether modal is open (required)
onClose() => void-Callback when modal should close (required)
size"sm" | "md" | "lg" | "xl" | "full""md"Modal size
closeOnOverlayClickbooleantrueClose when clicking backdrop
closeOnEscbooleantrueClose on Escape key
classNamestring-Additional CSS class on the overlay
aria-labelstring-Accessible name for the dialog
aria-labelledbystring-ID of an element that labels the dialog
childrenReactNode-Modal sections or content

ModalHeader, ModalBody, ModalFooter​

PropTypeDefaultDescription
classNamestring-Additional CSS class
childrenReactNode-Section content

Accessibility​

  • The overlay uses role="dialog" and aria-modal="true".
  • Give every modal an accessible name. Use aria-labelledby when a visible heading labels the dialog. Use aria-label when no visible heading is available.
  • Focus moves into the modal, stays inside it, and returns to the previously focused element when the modal closes.
  • Body scrolling stops while the modal is open.
  • Escape closes the modal by default.

TypeScript​

import { Modal, ModalHeader, ModalBody, ModalFooter, type ModalSize, type ModalProps, type ModalHeaderProps, type ModalBodyProps, type ModalFooterProps } from "@tosui/react";