Contents

How to Use Turbo Frames in Ruby on Rails: Modal Window Example

How to Use Turbo Frames in Ruby on Rails: Modal Window Example

Turbo Frames allow you to update specific parts of a page without reloading the entire page. This guide shows how to create a modal window for creating posts using Turbo Frames.

Turbo Frames let you divide your page into independent sections that can be updated separately. When you click a link or submit a form, only the content inside the matching Turbo Frame gets updated.

Create a new Rails project and generate a Posts scaffold:

rails new turbo_frames_example
cd turbo_frames_example
rails generate scaffold Post title:string body:text
rails db:migrate

Rails 7+ includes turbo-rails by default.

Open app/views/layouts/application.html.erb and add the modal frame before </body>:

<!DOCTYPE html>
<html>
  <head>
    <title>TurboFramesExample</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>
  <body>
    <%= yield %>

    <%= turbo_frame_tag :modal %>
  </body>
</html>

Key point: turbo_frame_tag :modal creates a container where Turbo will load content.

Update app/views/posts/new.html.erb:

<%= turbo_frame_tag :modal do %>
  <div>
    <h1>New Post</h1>
    <%= link_to "Close", posts_path, data: { turbo_frame: "_top" } %>
    <%= render "form", post: @post %>
  </div>
<% end %>

Key points:

  • The turbo_frame_tag :modal matches the ID in the layout
  • data: { turbo_frame: "_top" } on the close link navigates the entire page (exits the frame)

Modify app/views/posts/_form.html.erb:

<%= form_with(model: post, data: { turbo_frame: '_top' }) do |form| %>
  <% if post.errors.any? %>
    <div style="color: red">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
      <ul>
        <% post.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div>
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

Key point: data: { turbo_frame: '_top' } on the form ensures that after saving, the entire page redirects (not just the frame).

Update app/views/posts/index.html.erb:

<h1>Posts</h1>

<%= link_to "New Post", new_post_path, data: { turbo_frame: 'modal' } %>

<div id="posts">
  <% @posts.each do |post| %>
    <div>
      <h3><%= post.title %></h3>
      <p><%= post.body %></p>
      <%= link_to "Show", post %>
      <%= link_to "Edit", edit_post_path(post) %>
      <%= button_to "Delete", post, method: :delete %>
    </div>
  <% end %>
</div>

Key point: data: { turbo_frame: 'modal' } tells Turbo to load the response into the :modal frame instead of replacing the entire page.

  1. User clicks “New Post” link with data: { turbo_frame: 'modal' }
  2. Turbo intercepts the click and makes an AJAX request to /posts/new
  3. From the response, Turbo extracts content inside <turbo-frame id="modal">...</turbo-frame>
  4. Turbo injects that content into the turbo_frame_tag :modal in the layout
  5. When the form is submitted with data: { turbo_frame: '_top' }, the entire page redirects

Turbo matches frames by their ID. The turbo_frame_tag :modal in new.html.erb must match the one in application.html.erb.

  • data: { turbo_frame: "_top" } - navigates the entire page (breaks out of the frame)
  • Without this, the response would only update content inside the current frame
  • data: { turbo_frame: 'modal' } - loads the response into the specified frame
  • Without this, Turbo would replace the entire page

To also edit posts in a modal, update app/views/posts/edit.html.erb:

<%= turbo_frame_tag :modal do %>
  <div>
    <h1>Edit Post</h1>
    <%= link_to "Close", posts_path, data: { turbo_frame: "_top" } %>
    <%= render "form", post: @post %>
  </div>
<% end %>

And add data: { turbo_frame: 'modal' } to the edit link in index.html.erb:

<%= link_to "Edit", edit_post_path(post), data: { turbo_frame: 'modal' } %>

To use Turbo Frames for a modal window:

  1. Add <%= turbo_frame_tag :modal %> to your layout
  2. Wrap the content in your target view with <%= turbo_frame_tag :modal do %>
  3. Use data: { turbo_frame: 'modal' } on links that should open the modal
  4. Use data: { turbo_frame: '_top' } on forms and links that should exit the modal

That’s it! No JavaScript required.

Related Content