motion-dialog

Modal dialog with spring-physics animation. Slides up on open, springs back down on close. Built on the native <dialog> element for top-layer stacking and keyboard accessibility.

Preview

Spring dialog

Slides up with spring physics. The backdrop fades in simultaneously. Press Esc or use the button to close.

No backdrop

Add no-backdrop to remove the dimmed overlay. The dialog floats without any overlay.

No backdrop

Floating dialog with no dimmed overlay. Press Esc or use the button to close.

Light dismiss

Add light-dismiss to let users close by clicking the backdrop. By default, only Esc and explicit close actions work.

Light dismiss

Click the backdrop or press Esc to close. Without light-dismiss, only Esc and an explicit close button work.

Import

import 'motion-components/motion-dialog'   // just this one
import 'motion-components'               // or the whole library

Usage

<button onclick="document.querySelector('#dlg').show()">Open</button>

<motion-dialog id="dlg">
  <h2>Dialog title</h2>
  <p>Dialog content goes here.</p>
  <button onclick="this.closest('motion-dialog').close()">Close</button>
</motion-dialog>

Declarative open

Set the open attribute to control the dialog from markup or frameworks that bind attributes:

<!-- opens on mount -->
<motion-dialog open>
  <p>Shown immediately on page load.</p>
</motion-dialog>

Listening for open and close

The motion-open event fires when the dialog opens, as the entrance animation starts. The motion-close event fires after the exit animation completes. Use them to sync state:

import type { MotionDialog } from 'motion-components'

const dialog = document.querySelector<MotionDialog>('motion-dialog')!

dialog.addEventListener('motion-open', () => {
  console.log('dialog opened')
})

dialog.addEventListener('motion-close', () => {
  console.log('dialog closed')
})

Properties

Attribute Property Type Default Description
open open boolean false Opens the dialog when present; reflects to attribute
duration duration number 0.5 Spring duration for enter/exit animations, in seconds
bounce bounce number 0.25 Spring bounciness (0 = critically damped, higher = more elastic)
y y number 40 Initial vertical offset for the slide-up entrance, in pixels
no-backdrop noBackdrop boolean false When present, hides the dimmed/blurred overlay. The dialog floats without any overlay
light-dismiss lightDismiss boolean false When present, clicking the backdrop outside the panel closes the dialog

CSS custom properties

Style the panel and backdrop without reaching into shadow DOM:

Property Type Default Description
--mc-dialog-bg color var(--mc-color-surface, Canvas) Panel background
--mc-dialog-color color var(--mc-color-text, CanvasText) Panel text color
--mc-dialog-radius length 16px Panel corner radius
--mc-dialog-padding length 2rem Panel padding
--mc-dialog-max-width length min(560px, 100vw - 2rem) Maximum panel width
--mc-dialog-max-height length calc(100dvh - 4rem) Maximum panel height
--mc-dialog-shadow shadow 0 8px 32px … Panel box shadow. The default ends with a 1px ring in var(--mc-color-border, rgba(0, 0, 0, 0.08))
--mc-dialog-backdrop-color color rgba(0, 0, 0, 0.48) Backdrop overlay color
--mc-dialog-backdrop-blur filter blur(6px) Backdrop backdrop-filter
motion-dialog {
  --mc-dialog-bg: #1a1a2e;
  --mc-dialog-color: #e2e2ff;
  --mc-dialog-radius: 24px;
  --mc-dialog-backdrop-color: rgba(10, 10, 30, 0.6);
  --mc-dialog-backdrop-blur: blur(12px);
}

Methods

Method Description
show() Opens the dialog with the entrance animation.
close() Closes the dialog with the exit animation.

Events

Event Description
motion-open Fired when the dialog opens, as the entrance animation starts. Not fired when the dialog is reopened during its exit animation. Bubbles and is composed.
motion-close Fired after the close animation completes. Bubbles and is composed.

Accessibility

Uses the native <dialog> element opened via showModal(), which places it in the browser's top layer. Focus is automatically trapped inside the dialog while open, and Esc always closes it. By default, clicking the backdrop does not close the dialog. Add light-dismiss to enable that. Screen readers announce the dialog via the browser's built-in role="dialog" and aria-modal semantics. With prefers-reduced-motion active, all animations are skipped and the dialog appears instantly.