Components
Modal
Dialog overlay for focused tasks, confirmations, and forms.
Overview
The Modal component provides an overlay dialog with header, body, and footer sections. It supports custom sizing, fullscreen mode, actions (submit/button), and compound composition via primitives.
Basic Usage
import { Modal } from '@fsai/shared-ui';
<Modal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
title="Edit Profile"
action={{
type: 'button',
label: 'Save',
onClick: handleSave,
}}
>
<p>Modal body content goes here.</p>
</Modal>With Form Submit
Tie the action to a form using type: 'submit':
<Modal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
title="Create Item"
action={{
type: 'submit',
formId: 'create-item-form',
label: 'Create',
}}
>
<form id="create-item-form" onSubmit={handleSubmit}>
<Input label="Name" name="name" />
</form>
</Modal>Loading Action
<Modal
isOpen={isOpen}
handleClose={() => setIsOpen(false)}
title="Saving..."
action={{
type: 'button',
label: 'Save',
onClick: handleSave,
isLoading: isSaving,
}}
>
{children}
</Modal>Compound Components (Primitives)
For advanced layouts, use the modal primitives directly:
import { Modal } from '@fsai/shared-ui';
<Modal.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Modal.Backdrop />
<Modal.Panel>
<Modal.Header>
<Modal.Title>Custom Layout</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body>Content</Modal.Body>
<Modal.Footer>
<Button role="button" variant="secondary" onClick={() => setIsOpen(false)}>Cancel</Button>
<Button role="button" variant="primary" onClick={handleSave}>Save</Button>
</Modal.Footer>
</Modal.Panel>
</Modal.Root>Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | — | Required. Controls visibility |
title | ReactNode | — | Required. Modal heading |
children | ReactNode | — | Required. Modal body content |
handleClose | () => void | — | Called when modal should close |
action | ModalAction | — | Primary action button config |
subTitle | ReactNode | — | Subtitle below the title |
Icon | ComponentType | — | Icon in the header |
width | string | number | — | Custom width |
maxWidth | string | number | — | Maximum width |
height | string | number | — | Custom height |
maxHeight | string | number | — | Maximum height |
fullscreen | boolean | false | Expands to fill the viewport |
closeOnOutsideClick | boolean | true | Close when clicking the backdrop |
withPadding | boolean | true | Applies body padding |
hideHeader | boolean | false | Hides the header section |
secondaryLabel | string | 'Cancel' | Secondary button label |
footer | ReactNode | — | Custom footer content |
zIndex | number | 80 | CSS z-index |
ModalAction
| Prop | Type | Description |
|---|---|---|
type | 'button' | 'submit' | Required. Action type |
label | string | Button label |
onClick | () => void | Click handler (button type) |
formId | string | Form ID to submit (submit type) |
variant | ButtonVariant | Button variant |
isLoading | boolean | Shows loading spinner |
disabled | boolean | Disables the action |
icon | IconName | Action button icon |
Guidelines
- Do always provide a clear title that describes the task
- Do use
closeOnOutsideClickfor non-destructive modals - Do set
closeOnOutsideClick={false}for forms with unsaved changes - Don't nest modals inside modals
- Don't use modals for simple confirmations — use ConfirmActionModal instead
- Don't use fullscreen mode on desktop unless the content genuinely requires it