Mosmox UI
Current version
GitHubnpmjs.com
Overlays

Dialog

Modal and non-modal layered content with focus management, dismissal controls, and responsive sizes.

Dialog presents focused content above the current page. Set modal to block the rest of the interface with an overlay, or leave it unset for a non-modal floating dialog. The component manages focus, Escape dismissal, overlay dismissal, body scrolling, and focus restoration for modal usage.

Basic usage

The open prop makes the dialog controlled. Use onOpenChange to connect it to application state:

const [open, setOpen] = useState(false);

<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog
  modal
  open={open}
  onOpenChange={setOpen}
  title="Delete project"
  description="This action cannot be undone."
>
  <p>All project data will be permanently removed.</p>
</Dialog>

Use defaultOpen when the dialog should manage its own initial visibility. onClose runs whenever the dialog transitions to closed.

modal defaults to false:

  • modal adds an overlay, blocks page interaction, locks body scrolling, exposes aria-modal, and traps focus inside the dialog.
  • Without modal, the dialog is still rendered in a portal but does not add a backdrop or lock page scrolling. It can be used for contextual or temporary content.

Overlay dismissal is enabled by default for modal dialogs. Set closeOnOverlayClick={false} when the user must explicitly choose an action. Set closeOnEscape={false} only when Escape must be handled by content inside the dialog.

Entrance animation

Use animation to choose how the dialog enters. It defaults to none, so existing dialogs appear immediately. Available effects are top, bottom, left, right, and scale:

<Dialog modal animation="top" title="Animated dialog">
  <p>This dialog slides down from the top.</p>
</Dialog>

The directional effects slide the dialog in from the selected side. scale starts slightly larger and settles into its normal size. The animations respect prefers-reduced-motion and are disabled when the user requests reduced motion.

ValueEffect
noneOpens immediately without an entrance animation. This is the default.
topFades and slides the dialog down from above.
bottomFades and slides the dialog up from below.
leftFades and slides the dialog in from the left.
rightFades and slides the dialog in from the right.
scaleFades in while scaling down slightly to its normal size.

The parameter can be changed whenever the dialog is opened:

<Dialog
  modal
  animation="right"
  open={open}
  onOpenChange={setOpen}
  title="Details"
>
  <DetailsPanel />
</Dialog>

Use title and description for an accessible heading and supporting text. Use titleIcon to place a decorative icon before the title and closeButton={false} to hide the built-in close control. header completely replaces the standard header, including the title, description, and close button, so the consumer can render its own layout and close action. When using a custom header, provide an accessible name with aria-label if the visible custom content is not a heading. children is the scrollable content region. Dialogs can use built-in actions or a completely custom footer:

Header options

The default header combines the title, optional description, and built-in close button. These properties control the standard header without replacing it:

<Dialog
  modal
  title="Workspace settings"
  titleIcon={<Settings aria-hidden="true" size={16} />}
  closeButton={false}
>
  <SettingsForm />
</Dialog>

Use header when the complete header needs a custom structure. The custom content is rendered inside the dialog header area and must include its own close control when one is needed. A controlled dialog can close it through onOpenChange:

const [open, setOpen] = useState(false);

<Dialog
  modal
  open={open}
  onOpenChange={setOpen}
  aria-label="Custom workspace dialog"
  header={(
    <div className="custom-dialog-header">
      <strong>Workspace</strong>
      <Button aria-label="Close" onClick={() => setOpen(false)}>Close</Button>
    </div>
  )}
>
  <WorkspaceSettings />
</Dialog>

header takes precedence over title, titleIcon, description, and closeButton; those standard header properties are not rendered while a custom header is supplied.

The standard action footer is enabled with showActions. It renders Cancel and OK by default. Without custom click handlers, both buttons close the dialog:

<Dialog modal title="Save changes?" showActions>
  <p>Your edits are ready to be saved.</p>
</Dialog>

When the dialog uses size="sm" or size="xs", its standard action buttons automatically use the matching compact button size. Set actions.confirm.size or actions.cancel.size to override an individual button. Extra-small dialogs do not render description, keeping the compact header focused on the title.

Use actions to configure either standard button. confirm controls the positive/primary action and cancel controls the secondary action. Each button can have its own text, icon, visual variant, size, loading state, disabled state, native button type, accessible label, and click behavior:

import { Check, FloppyDisk } from '@mosmox-ui/icons';

<Dialog
  modal
  title="Save changes"
  actions={{
    confirm: {
      label: 'Save',
      icon: <FloppyDisk />,
      variant: 'primary',
      onClick: saveChanges,
    },
    cancel: {
      label: 'Cancel',
      icon: <Check />,
      variant: 'outline',
      onClick: closeDialog,
    },
  }}
>
  <p>Your changes are ready to be saved.</p>
</Dialog>

The handler receives the native button event. A custom handler replaces the default close behavior, so it can save data, validate a form, start an async operation, or close the dialog manually with setOpen(false). Use loading and loadingLabel while an action is processing:

<Dialog
  modal
  title="Publish document"
  actions={{
    confirm: {
      label: 'Publish',
      loading: isPublishing,
      loadingLabel: 'Publishing',
      onClick: publishDocument,
    },
    cancel: { label: 'Cancel', onClick: closeDialog },
  }}
>
  <DocumentSummary />
</Dialog>

Set either action to false to show only the other button, for example actions={{ confirm: false }} for a Cancel-only dialog. Use actions.order to change the button order: ['cancel', 'confirm'] places Cancel first, while ['confirm', 'cancel'] places the primary action first. Use actionsAlign or actions.align with start, center, end, or between to position the button group.

Content opposite the actions

Use footerAside to add optional content on the side opposite the standard action buttons. It can contain text, links, status information, or additional controls. When actions are aligned to the end, the aside is placed on the left; when actions are aligned to the start, the aside is placed on the right. It is empty by default and is ignored when a fully custom footer is provided:

<Dialog
  modal
  title="Save changes"
  footerAside={<span>Last saved just now</span>}
  actions={{
    confirm: { label: 'Save', onClick: saveChanges },
    cancel: { label: 'Cancel', onClick: closeDialog },
  }}
>
  <SettingsForm />
</Dialog>

Pass footer when the entire footer needs custom content. A custom footer takes precedence over actions and showActions:

<Dialog
  modal
  title="Invite a teammate"
  description="Send an invitation to join this workspace."
  footer={
    <>
      <Button variant="outline">Cancel</Button>
      <Button>Send invitation</Button>
    </>
  }
>
  <Input label="Email address" type="email" />
</Dialog>

Set closeButton={false} when the dialog has its own explicit close action. Keep an accessible title or provide aria-label when omitting the visible title.

Sizes and placement

size supports four standard dialog sizes: xs (extra small), sm (small), default, and lg (large). These sizes work identically for modal and non-modal dialogs. The content area scrolls when it exceeds the viewport, while the header and footer remain visible.

Use placement to control the vertical position. It defaults to center. Set placement="top" to place the dialog near the top of the viewport with a 50px offset on larger screens, or placement="bottom" to align it to the bottom.

The component also keeps two extended sizes for existing use cases: xl for an extra-wide dialog and full for a full-screen workspace. Use them when the dialog needs more space than the standard large size.

<Dialog modal size="xs" title="Quick action">
  <p>Compact content for a short task.</p>
</Dialog>

<Dialog modal size="sm" title="Saved">
  <p>Your changes were saved.</p>
</Dialog>

<Dialog modal size="default" title="Review changes">
  <p>The default dialog size is suitable for most content.</p>
</Dialog>

<Dialog modal size="lg" title="Invite a teammate">
  <p>Use the large size for forms and more detailed content.</p>
</Dialog>

<Dialog modal placement="top" title="Draft saved">
  <p>This dialog starts 50px from the top edge.</p>
</Dialog>

<Dialog modal size="full" title="Focused workspace">
  <p>Use the full size for multi-step or immersive workflows.</p>
</Dialog>

State and dismissal

onOpenChange is called for programmatic changes and user dismissal. onClose is a convenient close-only callback. The close button, Escape key, and backdrop use the same close path, so controlled consumers receive one consistent state update.

<Dialog
  modal
  open={open}
  onOpenChange={setOpen}
  closeOnOverlayClick={false}
  onClose={() => logEvent('dialog_closed')}
  title="Confirm payment"
>
  <PaymentForm onComplete={() => setOpen(false)} />
</Dialog>

Accessibility and focus behavior

The component renders a semantic dialog element and connects title and description with aria-labelledby and aria-describedby. Modal dialogs:

  • move focus into the dialog when opened;
  • keep Tab and Shift+Tab inside the dialog;
  • return focus to the previously focused element when closed;
  • prevent page scrolling while open;
  • support Escape dismissal when enabled.

Interactive controls inside the dialog should have visible labels and clear accessible names. A modal should always provide a meaningful title or an equivalent accessible name.

API Reference

PropertyTypeDefaultDescription
openboolean—Controlled visibility.
defaultOpenbooleanfalseInitial visibility for uncontrolled usage.
onOpenChange(open: boolean) => void—Called when visibility changes.
onClose() => void—Called when the dialog closes.
modalbooleanfalseAdds overlay, focus trap, scroll lock, and modal semantics.
closeOnEscapebooleantrueAllows Escape to close the dialog.
closeOnOverlayClickbooleantrueAllows clicking the modal backdrop to close it.
closeButtonbooleantrueShows the built-in close button.
titleReactNode—Accessible visible heading.
aria-labelstring—Accessible name when there is no visible title.
descriptionReactNode—Supporting text connected to the dialog.
headerReactNode—Completely replaces the standard header, including title, description, and built-in close button.
titleIconReactNode—Decorative icon rendered before the standard title.
childrenReactNode—Scrollable dialog content.
footerReactNode—Fully custom footer content. Takes precedence over standard actions.
showActionsbooleanfalseShows default Cancel and OK actions.
actionsDialogActions—Configures standard cancel and confirm actions.
actionsAlignstart | center | end | betweenendAlignment for standard actions.
animationnone | top | bottom | left | right | scalenoneEntrance animation for the dialog.
footerAsideReactNode—Content rendered opposite the standard action buttons.
sizexs | sm | default | lg | xl | fulldefaultDialog width. The standard sizes are extra small, small, default, and large; xl and full are extended wide/full-screen modes.
placementcenter | top | bottomcenterVertical placement within the viewport.
idstringgeneratedBase ID for dialog, title, and description.
classNamestring—Additional class on the dialog panel.

DialogActions and DialogAction

actions.cancel and actions.confirm accept false to hide one action. Otherwise each action supports label, icon, variant, size, onClick, disabled, loading, loadingLabel, type, and aria-label. The default labels are Cancel and OK; without a custom handler, either standard button closes the dialog.

Theme tokens

Dialog uses shared theme tokens for surfaces, text, borders, focus, radius, typography, and spacing. Component-only layout rules are kept in Dialog.css.

TokenPurpose
--mm-color-surfaceDialog surface.
--mm-color-textTitle and content text.
--mm-color-muted-textDescription text.
--mm-color-control-borderPanel and section borders.
--mm-color-control-filledClose-button hover surface.
--mm-color-control-iconClose icon color.
--mm-color-focusKeyboard focus ring.
--mm-color-overlayLight modal backdrop color.
--mm-overlay-blurShared modal backdrop blur used by Dialog and Drawer. Defaults to 2px.
--mm-radius-controlDialog and control radius.
--mm-font-family-sansDialog typography.

Override shared values globally through ConfigProvider so Dialog follows the same theme as the other components.