A long-lived project that still receives updates
An easy-to-use, flexible, and powerful Turbo Modal solution for Rails 8+ built with Stimulus.js, Tailwind CSS (or vanilla CSS) and Hotwire.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

The Ultimate Turbo Modal for Rails (UTMR)

There are MANY Turbo/Hotwire/Stimulus modal dialog implementations out there. However, as you may have learned, the majority fall short in different, often subtle ways. They generally cover the basics quite well, but do not check all the boxes for real-world use.

UTMR aims to be the be-all and end-all of Turbo Modals. I believe it is the best (only?) full-featured implementation and checks all the boxes. It is feature-rich, yet extremely easy to use. Its purpose is to make it as easy as possible to have polished Turbo-backed modals and drawers.

Under the hood, it uses Stimulus, Turbo, the native HTML <dialog> element, and Idiomorph.

It ships in two flavors: Tailwind (v4+) and vanilla CSS. It is easy to create your own flavor to suit your needs.

Screenshots & Demo Video

Demo Video

Light Modal Form Light Long Scrollable Modal
Light Drawer with Footer Dark Modal Form

Installation

$ bundle add ultimate_turbo_modal
$ bundle exec rails g ultimate_turbo_modal:install

Usage

  1. Wrap your view inside a modal block as follow:
<%= modal do %>
  Hello World!
<% end %>
  1. Link to your view by specifying modal as the target Turbo Frame:
<%= link_to "Open Modal", "/hello_world", data: { turbo_frame: "modal" } %>

Clicking on the link will automatically open the content of the view inside a modal. If you open the link in a new tab, it will render normally outside of the modal. Nothing to do!

This is really all you should need to do for most use cases.

Please note: The generator automatically adds <turbo-frame id="modal"></turbo-frame> to your application layout. If you need to open modals or drawers in another layout, please add this HTML snippet manually.

Setting Title and Footer

You can set a custom title and footer by passing a block. For example:

<%= modal do |m| %>
  <% m.title do %>
    <div>My Title</div>
  <% end %>

  <p>Your modal body</p>
  <%= form_with url: "#", html: { id: "myform" } do |f| %>
    <p>..</p>
  <% end %>

  <% m.footer do %>
    <input type="submit" form="myform">Submit</input>
  <% end %>
<% end %>

You can also set a title with options (see below).

Detecting modal at render time

If you need to do something a little bit more advanced when the view is shown outside of a modal, you can use the #inside_modal? method as such:

<% if inside_modal? %>
  <h1 class="text-2xl mb-8">Hello from modal</h1>
<% else %>
  <h1 class="text-2xl mb-8">Hello from a normal page render</h1>
<% end %>

   

Options

Do not get overwhelmed with all the options. The defaults are sensible. You can change the defaults with an initializer:

# config/initializers/ultimate_turbo_modal.rb

UltimateTurboModal.configure do |config|
  config.flavor = :tailwind
  config.allowed_click_outside_selector = []

  config.modal do |m|
    m.advance = false
    m.close_button = true
    m.header = true
    m.header_divider = true
    m.footer_divider = true
    m.padding = true
    m.overlay = true
  end

  config.drawer do |d|
    d.position = :right
    d.close_button = true
    d.header = true
    d.header_divider = false
    d.footer_divider = true
    d.padding = true
    d.overlay = true
    d.size = :md
  end
end

Per-instance options passed to modal() or drawer() override the defaults.

Modal Options

Name Default Description
advance false When opening the modal, the URL in the URL bar will change to the URL of the view being shown in the modal. The Back button dismisses the modal and navigates back. If a URL is specified as a string (e.g. advance: "/other-path"), the browser history will advance, and the URL shown in the URL bar will be replaced with the value specified.
close_button true Shows or hide a close button (X) at the top right of the modal.
header true Whether to display a modal header.
header_divider true Whether to display a divider below the header.
footer_divider true Whether to display a divider above the footer.
padding true Adds padding inside the modal.
overlay true Whether to show a backdrop overlay.
title nil Title to display in the modal header. Alternatively, you can set the title with a block.

Example usage with options

<%= modal(padding: true, close_button: false, advance: false) do %>
  Hello World!
<% end %>
<%= modal(padding: true, close_button: false, advance: "/foo/bar") do %>
  Hello World!
<% end %>

Drawers

UTMR includes built-in drawer (slide-out panel) support. Drawers share the same <dialog> element and Stimulus controller as modals — no additional JavaScript required.

Basic Usage

Use the drawer helper instead of modal:

<%= drawer do %>
  Drawer content here!
<% end %>

Link to it the same way as a modal:

<%= link_to "Open Drawer", "/settings", data: { turbo_frame: "modal" } %>

Drawer Options

Name Default Description
position :right Which edge the drawer slides from. :right or :left.
size :md Width of the drawer. One of :xs, :sm, :md, :lg, :xl, :"2xl", :full, or a CSS string (e.g. "500px").
overlay true Whether to show a backdrop overlay behind the drawer.
close_button true Shows or hide a close button (X).
header true Whether to display a header.
header_divider false Whether to display a divider below the header.
footer_divider true Whether to display a divider above the footer.
padding true Adds padding inside the drawer.
title nil Title to display in the drawer header.
<%= drawer(position: :left, size: :lg, overlay: false, title: "Settings") do %>
  <p>Drawer content</p>
<% end %>

Drawer Size Reference

Size Max Width
:sm 24rem (384px)
:md 28rem (448px)
:lg 42rem (672px)
:xl 56rem (896px)
:full Full viewport width minus a small gutter
CSS string Custom value, e.g. "500px" or "50vw"

Features and capabilities

  • Extremely easy to use
  • Built-in drawer (slide-out panel) support with left/right positioning and configurable sizes
  • Fully responsive
  • Does not break if a user navigates directly to a page that is usually shown in a modal
  • Opening a modal in a new browser tab (ie: right click) gracefully degrades without having to code a modal and non-modal version of the same page
  • Automatically handles URL history (ie: pushState) for shareable URLs
  • pushState URL optionally overrideable
  • Seamless support for multi-page navigation within the modal
  • Seamless support for forms with validations
  • Seamless support for Rails flash messages
  • Support for long, scrollable modals
  • Properly locks the background page when scrolling a long modal
  • Click outside the modal to dismiss
  • Option to whitelist CSS selectors that won't dismiss the modal when clicked outside the modal (see body-appended widgets guide for datepickers and similar popups)
  • Keyboard control; ESC to dismiss
  • Automatic (or not) close button
  • Native focus trapping via the <dialog> element for improved accessibility (Tab and Shift+Tab cycle through focusable elements within the modal only)
  • Smooth redirects: form submissions that redirect back to the same page morph the content behind the modal before closing; redirects to a different page close the modal with animation first, then navigate

Running the Demo Application

The repository includes a demo application in the demo-app directory that showcases all the features of Ultimate Turbo Modal. To run it locally:

# Navigate to the demo app directory
cd demo-app

# Start the development server
bin/dev

# Open your browser
open http://localhost:3000

Upgrading

Please see the Upgrading Guide for detailed instructions on upgrading between versions.

Thanks

Thanks to @joeldrapper and @konnorrogers for all the help!

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/cmer/ultimate_turbo_modal.

License

The gem is available as open source under the terms of the MIT License.

Star History

Star History Chart