Riposte

Modal

A controlled dialog over a dimming scrim, layered through a core portal and animated in and out via usePresence. You own open and react to onClose, which fires from the…

A controlled dialog over a dimming scrim, layered through a core portal and animated in and out via usePresence. You own open and react to onClose, which fires from the close button, a scrim click (when maskClosable), and Escape (when closeOnEsc). children are the body; title and footer are optional slots:

val (open, setOpen, _) = useState(false)

Button("Open", onClick = () => setOpen(true))

Modal(
  open = open,
  onClose = () => setOpen(false),
  title = Some(span("Delete file?")),
  footer = Some(div(
    Button("Cancel", onClick = () => setOpen(false)),
    Button("Delete", color = Color.Error, onClick = () => { remove(); setOpen(false) }),
  )),
)(
  p("This can't be undone."),
)

It implements the full dialog ARIA pattern: role="dialog" (or alertdialog with alert = true) and aria-modal, labelled by the title (or an explicit ariaLabel), focus moved into the dialog on open and restored to the opener on close, Tab trapped inside, and Escape to close. centered vertically centres the box, width overrides its width (capped at 90vw), and closable toggles the corner close button. exitMs (default 200) is how long the close animation runs before unmount — keep it in step with the skin’s transition.

Search

Esc
to navigate to open Esc to close