Modals are dialogs displayed over inert content that allow users to complete a singular, focused task without leaving the page.

Modal

Usage

  • Use Modal for tasks requiring the user's immediate attention: action confirmations, additional tasks too small for their own page, and feature moments. Use feature moments sparingly.
  • Never nest Modals. Rework the flow, move the content inline or onto another page, or use a Popover instead.
  • Use Modal when users must confirm a task or provide input before continuing. Use Drawer for content with less contextual relation to the main page. Use Popover for small, inline supplementary content.
  • Keep Modal content focused on a single task. Move multi-step workflows or lengthy content to a dedicated page.
  • Reach for an established configuration before creating a new Modal design:
    • Confirmation — hide the close button (shouldShowCloseButton={false}) and assemble ModalBody with a ModalFooter holding secondary and primary actions.
    • Destructive confirmation — hide the close button, put a confirmation input in ModalBody, and disable the primary action (isDisabled) until input requirements are met. Mark the primary action isDanger.
    • Form — disable overlay-click dismissal (shouldCloseOnOverlayClick={false}) to prevent accidental data loss. Put submit and cancel buttons in ModalFooter.
    • Save/discard — hide the close button and give ModalFooter three actions: a go-back action pushed left with mr="auto", a discard action (isDanger), and a primary save action.
    • Informational — display read-only content in ModalBody with a single primary dismiss button in ModalFooter.
    • Feature moment — add ModalSplit for the graphic panel, hide the close button, and include a single call-to-action in ModalFooter.

Types

  • Standard (default) — single-column layout with an optional header and footer and scrollable body content.
  • Split — two equal columns using ModalSplit. Place ModalSplit before ModalBody for a left panel, after it for a right panel.

Appearance

  • Size (size, default "md") — use "md" for most use cases. Use "sm" for shorter content and smaller viewports.
  • Close button (shouldShowCloseButton, default true) — hide it only when the Modal requires an explicit user decision, such as deleting data. Always provide a cancel action in the footer when the close button is hidden.
  • Inset (ModalInset) — renders content edge-to-edge over the Modal's default padding. Use for full-width backgrounds, images, or dividers that bleed to the Modal's edges.

Behaviors

  • Dismissal methods — close button, action button, overlay click, and the Escape key. All are enabled by default.
  • Non-dismissable shortcut (isDismissable={false}) — sets shouldShowCloseButton, shouldCloseOnOverlayClick, and shouldCloseOnEscapePress to false in one prop. Each individual prop still wins when explicitly set. Reserve non-dismissable Modals for cases where user interaction is mandatory.
  • Overlay click dismissal (shouldCloseOnOverlayClick, default true) — disable for form Modals to prevent accidental data loss.
  • Escape key dismissal (shouldCloseOnEscapePress, default true) — disable alongside the close button for Modals that require an explicit choice.
  • Scrolling (shouldScrollInViewport, default false) — by default the body scrolls while the header and footer stay fixed. Set to true to scroll the entire Modal within the viewport.
  • Animation (animationTransitionDuration) — the Modal fades and slides down; the overlay fades independently. Pass a duration in milliseconds to customize it.

Content

  • Write titles in sentence case following the pattern "action + object" (e.g., "Delete campaign?"). Include the object — a noun or noun phrase tied to the workflow.
  • End the title with a question mark when asking for confirmation. Omit ending punctuation otherwise.
  • Never use generic confirmation titles such as "Are you sure?" or "Delete?"
  • State the consequence in the body. Explicitly note when an action is irreversible or name the recovery window.
  • Start footer action labels with a verb describing the action. Keep labels to 1–3 words in sentence case with no period.
  • Prefer specific labels, but generic labels are acceptable when the context makes the action clear. Be consistent in verbiage across the application.
  • Keep body content concise and focused on a single task.

Implementation Notes

  • Compose Modal from subcomponents: ModalBody for body content, ModalFooter for action buttons, ModalSplit for split-layout panels, and ModalInset for edge-to-edge content. Pass them as children in React, or in the default block in Ember.
  • Modal renders the overlay and dialog together. ModalDialog renders only the dialog (no overlay, no open/close state) — use it for static rendering such as documentation examples, not for real dialogs.
  • title renders the default header, which composes ModalHeader, ModalTitle, and ModalCloseButton. Omitting title renders no header at all — and therefore no close button, since the close button lives in the header.
  • shouldShowCloseButton with isDismissable={false} renders the close button in a disabled state rather than hiding it.
  • ModalFooter wraps ButtonGroup with groupVariant="spaced" — pass Button components directly as children.
  • ModalSplit panel side is controlled by DOM order relative to ModalBody, not by a prop.
  • ModalBody is a ModalInset with inheritPadding defaulting to true. Set inheritPadding on ModalInset to opt back into the Modal's padding.
  • Control visibility with isOpen and onClose. onClose receives (event, reason), where reason is "close-button-press" or a floating-ui open-change reason for overlay click and escape press.
  • Lifecycle callbacks: onOpen fires when the Modal opens, onCloseComplete fires after the close animation finishes.
  • initialFocusIndex overrides which focusable element receives focus on open (default is the first).
  • state accepts all other Modal props as a single object, for use with a ModalsManager pattern. Props passed directly override matching keys in state.
  • Ember invocation: <PlumaModal @isOpen={{this.isOpen}} @onClose={{this.handleClose}} @title="Modal title">. Subcomponents are <PlumaModalBody>, <PlumaModalFooter>, <PlumaModalSplit>, <PlumaModalInset>, and <PlumaModalDialog>.
  • Ember's default block yields the modal primitive context: <PlumaModal ... as |ctx|>.
  • Set a global animationTransitionDuration default via PlumaProvider's componentConfig.PlumaModal.

On this page