Best practice for Modal Management in modern website

I’m looking for clear guidance, best practices, and community feedback on how to manage modals in a modern Rails application using Hotwire and Stimulus.

Hotwire and Stimulus have now matured, and many developers have explored various modal-related patterns. However, I still haven’t found a commonly accepted or “official” approach to handling modals in this stack.

Are there recommended patterns, libraries, or gems for robust modal management?

Concrete example: I need to open a modal that contains a rich-text editor (e.g., TinyMCE) and perform GET/POST requests inside the workflow. I’m particularly interested in how others structure this (Turbo Frames? Stimulus controllers? custom JS?), and what has worked—or not worked—for them.

Additional question: At what point do the complexity and limitations of Hotwire/Stimulus push you to consider using a dedicated frontend framework or library (React, Vue, Svelte, Alpine, etc.) instead? What criteria or pain points usually trigger that decision?

Thanks for any insights or experience you can share!

Hi @sebastien ,

The best resource on this is probably the hotrails.dev article, How to make modals in a Rails application using Hotwire? . This approach utilizes the form to show/hide the modal as needed.

I follow essentially the same strategy, but I use event hooks (rather than Stimulus) to tie Hotwire to the CSS/JS framework. Here is how I do it in my applications if you are interested:

HTML:

  1. Add a turbo-frame element within the modal (e.g. “location-modal”)
  2. Add a matching turbo-frame element in the view to be rendered (e.g. new.html.erb)
  3. Add “data: { turbo_frame: “location-modal”" }” to your link (to trigger a turbo-frame response from outside of the turbo-frame within the hidden modal)

JS:

  1. Listen for the “turbo:frame-load” event, and use this to show the modal
  2. Listen for the “turbo:submit-end” event, and use this to hide the modal for a successful form submission

NOTE: the JS for this will vary depending on which CSS/JS framework you are using. Let me know if you are interested in seeing mine (Bootstrap based).

Custom Turbo Stream response:

To handle a successful form submission, you will likely want to add a custom turbo_stream response, both to clear the modal and to append the new/updated item to the page (since these locations are separate). Here is an example:

Controller (within the “create” action):

if @location.save
  format.html { redirect_to @location, notice: "Location was successfully created." }
  format.json { render :show, status: :created, location: @location }       
  format.turbo_stream
else
  format.html { render :new, status: :unprocessable_entity }
  format.json { render json: @location.errors, status: :unprocessable_entity }    end

View (app/views/locations/create.turbo_stream.erb)

<%= turbo_stream.append "locations", partial: "location", locals: { location: @location } %>
<%= turbo_stream.update "location-modal", "" %>

Additional JS components

For additional JS components (e.g. TinyMCE, ActiveText, or otherwise), you can tie the initialization of these components to the same “turbo:frame-load” event like so:

$(document).on('turbo:frame-load', "#location-modal", event => {
  init_custom_fields();
});
1 Like

Various articles here to help with this?

Though I am working using the native dialog element with Turbo frame more too lately (probably a good follow-up article, I think).

1 Like