Components

Modal

Modals are used to overlay content above an interface. They are intended to capture the user's attention in order to inform or shift focus to a pertinent task.

Modals can be used in various ways such as to confirm important or potentially destructive actions (e.g., deleting a message or upgrading a plan), to provide additional information, and to block user interaction in order to force action on critical tasks (e.g., billing errors, disconnected accounts, service outages, etc...)

Pro tip
**Modal V2 is now the recommended approach**. The legacy Modal (V1) is deprecated and will be removed in a future version. This section compares both versions and provides migration guidance.

Key Differences

FeatureModal V2Legacy Modal (V1)
State Managementopen / defaultOpen with onOpenChangeisOpen with onClose
AccessibilityBuilt on Radix UI Dialog with automatic ARIA attributesRequires manual appElementSelector for focus management
Header APItitle/subtitle props or ModalHeader componentModal.Header subcomponent
Content APIModalBody componentModal.Content subcomponent
Footer APIModalFooter with button propsModal.Footer with children
Close ButtonAutomatic in floating railModal.CloseButton subcomponent
TypeScriptStrong type safety with discriminated unionsBasic types
MobileResponsive bottom sheet layoutDesktop-focused
DraggableBuilt-in supportNot available

Migration Guide

Basic Modal

Legacy (V1):

const [open, setOpen] = useState(false)
<Modal
isOpen={open}
onClose={() => setOpen(false)}
label="Example Modal"
appElementSelector="#root"
>
<Modal.Header title="Title" subtitle="Subtitle" />
<Modal.Content>Content</Modal.Content>
<Modal.Footer>Footer</Modal.Footer>
</Modal>

Modal V2:

const [open, setOpen] = useState(false)
<ModalV2
open={open}
onOpenChange={setOpen}
title="Title"
subtitle="Subtitle"
aria-label="Example Modal"
>
<ModalBody>Content</ModalBody>
<ModalFooter primaryButton={<Button>Action</Button>} />
</ModalV2>

Controlled State

Legacy (V1):

  • Uses isOpen prop (boolean)
  • Uses onClose callback (no parameters)

Modal V2:

  • Uses open prop (boolean)
  • Uses onOpenChange callback (receives new boolean state)

Legacy (V1):

<Modal.Header
title="Title"
subtitle="Subtitle"
/>
// or custom
<Modal.Header bordered>
<CustomContent />
</Modal.Header>

Modal V2:

// Simple header (recommended)
<ModalV2 title="Title" subtitle="Subtitle" />
// Or explicit component
<ModalHeader title="Title" subtitle="Subtitle" />
// Custom header
<ModalCustomHeader>
<CustomContent />
</ModalCustomHeader>

Content

Legacy (V1):

<Modal.Content>
Content here
</Modal.Content>

Modal V2:

<ModalBody>
Content here
</ModalBody>

Legacy (V1):

<Modal.Footer>
<Box display="flex" justifyContent="flex-end">
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleSave}>Save</Button>
</Box>
</Modal.Footer>

Modal V2:

// Buttons automatically close modal
<ModalFooter
cancelButton={<Button>Cancel</Button>}
primaryButton={<Button>Save</Button>}
/>
// Or custom footer
<ModalCustomFooter>
<CustomContent />
</ModalCustomFooter>

Close Button

Legacy (V1):

<Modal.CloseButton />

Modal V2:

  • Close button is automatically rendered in the floating action rail
  • No need to manually add it
  • Use closeButtonAriaLabel prop to customize the label

Trigger Button

Legacy (V1):

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

Modal V2:

// Option 1: Manual trigger (same as V1)
<Button onClick={() => setOpen(true)}>Open</Button>
// Option 2: Built-in trigger (new)
<ModalV2
modalTrigger={<Button>Open</Button>}
defaultOpen={false}
>
Content
</ModalV2>

Accessibility

Legacy (V1):

  • Requires appElementSelector prop for focus management
  • Requires label prop for accessibility
  • Manual focus trapping setup

Modal V2:

  • Automatic focus management (no appElementSelector needed)
  • aria-label required only when no title/subtitle provided
  • Built-in focus trapping via Radix UI

Common Migration Patterns

Pattern 1: Simple Confirmation Modal

Legacy (V1):

<Modal
isOpen={open}
onClose={() => setOpen(false)}
label="Confirm"
>
<Modal.Header title="Delete?" />
<Modal.Content>
<Text>Are you sure?</Text>
</Modal.Content>
<Modal.Footer>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={handleDelete}>Delete</Button>
</Modal.Footer>
</Modal>

Modal V2:

<ModalV2
open={open}
onOpenChange={setOpen}
title="Delete?"
>
<ModalBody>
<Text>Are you sure?</Text>
</ModalBody>
<ModalFooter
cancelButton={<Button>Cancel</Button>}
primaryButton={<Button appearance="destructive">Delete</Button>}
/>
</ModalV2>

Pattern 2: Form Modal

Legacy (V1):

<Modal
isOpen={open}
onClose={() => setOpen(false)}
label="Edit Profile"
appElementSelector="#root"
>
<Modal.Header title="Edit Profile" />
<Modal.Content>
<Form>...</Form>
</Modal.Content>
<Modal.Footer>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={handleSave}>Save</Button>
</Modal.Footer>
</Modal>

Modal V2:

<ModalV2
open={open}
onOpenChange={setOpen}
title="Edit Profile"
>
<ModalBody>
<Form>...</Form>
</ModalBody>
<ModalFooter
cancelButton={<Button>Cancel</Button>}
primaryButton={<Button>Save</Button>}
/>
</ModalV2>

Breaking Changes

  1. Prop Names: isOpenopen, onCloseonOpenChange
  2. Component Names: Modal.HeaderModalHeader or title prop
  3. Component Names: Modal.ContentModalBody
  4. Component Names: Modal.FooterModalFooter (with different API)
  5. Removed: appElementSelector (no longer needed)
  6. Removed: zIndex prop (handled automatically)
  7. Removed: Modal.CloseButton (automatic in rail)
  8. Changed: Footer buttons automatically close modal (use ModalCloseWrapper if you need to prevent this)

Backdrop

All modals utilize a backdrop that overlays the site content and sits below the modal window. The backdrop dials back app UI and provides focus for the window and it's content.

Color
Opacity68%

Window

The Modal window houses content, and controls.

A modal window
Color
Border radius600 (8px)
Elevation300

Modal V2 provides a simplified header API. You can use the title and subtitle props on the root ModalV2 component, or use the ModalHeader component directly.

Simple Header (Recommended):

Custom Header:

For custom headers with actions, use ModalCustomHeader:

Title

The title appears in the top left corner of the modal window. A title isn't required, but is strongly recommended for most typical applications of Modal. When using title prop, aria-label becomes optional.

Actions

Actions are typically used in Modal Header when the content of the modal is free form and the subsequent action isn't dependent on the completion of a linear process.

Pro tip
Use of actions are on an as needed basis. The order from right to left is relative to the actions used.
ActionOrder (lowest to highest, right to left)
Primary1
Secondary2
Cancel (text)3

Close

The , used to close the modal, is always present in the floating action rail on the right side of the modal. This is automatic and doesn't need to be manually added.

Cancel

  • "Cancel", as a standalone action, always begins with a capital letter.
  • destructive actions should always be paired with a clear option to cancel.

The content we use in a modal is flexible to accommodate a wide range of use cases and needs. We can offer some rough direction on some common elements.

Text

  • Text in the body of a modal should almost always be left aligned.
  • Except when paired with expressive illustrations or imagery. In this case, we typically text align center.
  • Try to keep line lengths to around 15 words or 75% of the width of your window.

Illustrations and imagery

  • Illustrations and imagery can use white backgrounds or full bleed color.
  • When pairing illustrations and imagery with text, consider center aligning text in expressive environments.
  • If the imagery is being used in a more productive, supportive, long-form role where legibility is the primary concern, left aligned text can be used.
Modal with productive content
Modal with expressive content
Modal with multi-column content

Forms

Using a form in a modal follows all the same principles as using a form anywhere else. For more detailed information about how to use forms, take a look at our forms documentation.

Modal example with form
  • Forms should be a single column when possible.
  • Group related information in a logical sequence.
  • Tailor the length of form fields based on the content.
  • Use labels to describe form fields.

Modal Footer allows us to add actions or informational text to the bottom of a modal. Modal V2 provides a simplified API with automatic button wrapping.

Standard Footer:

Footer with Left Action:

Custom Footer:

For custom footers with informational text or complex layouts, use ModalCustomFooter:

Information

Sometimes it's necessary to provide additional information related to the modal content. Use ModalCustomFooter for this.

Actions

Actions are typically used in Modal Footer when the content of the modal is a linear experience, like a form, and the subsequent action depends on its completion.

Pro tip
Use of actions are on an as needed basis. The order from right to left is relative to the actions used.
ActionOrder (lowest to highest, right to left)
Primary1
Secondary2
Cancel (text)3

Note: Buttons passed to ModalFooter automatically close the modal when clicked. If you need to prevent this behavior, use ModalCustomFooter with ModalCloseWrapper only where needed.

Best Practices

  • An action taken from a modal should not open another subsequent modal
  • Since a modal draws the user's attention from other tasks and requires an action to be taken in order to close it, consider the user and necessity of the modal.