um

Turbo

Modal forms

One shared modal any link can load a form into, and a responder that saves and repaints the page once.

One <dialog> in the layout, around a turbo frame. A link aimed at the frame loads a page's form into it, and the same action renders a full page when it's visited directly, so there's no second template for the modal.

1. Mount the modal

Render it once, in the layout:

The dialog opens as soon as the frame starts fetching, with a skeleton until the response lands. A failed load shows an error with a retry. Escape, a click on the backdrop or the close button closes it.

2. Link to a form

modal_link_to takes link_to's arguments. side: :start or :end opens it as a drawer.

3. Render the form in a dialog

dialog form: takes form_with's options and builds the form around the whole panel, so a submit in the footer is inside it. When the request is for the modal frame, the dialog wraps itself in that frame; otherwise it renders as it is.

4. Respond from the controller

DialogResponder answers a save with turbo_stream.refresh, which morphs the page behind the modal. The modal closes in that same render, so the page repaints once, straight to the new state. A request without Turbo gets a plain redirect. A failed save renders the form again with :unprocessable_content, and the dialog stays open showing the errors.

The refresh needs turbo_refresh_method_tag :morph in the layout to keep scroll position and state. A plain redirect_to works too: the modal visits the page it lands on and closes. A response that renders into the frame, such as a wizard's next step, keeps the dialog open.

The frame's id is "modal". Change it with config.modal_frame_id. Needs import "unmagic/components/modal".

See it working