Introduction#
In my journey to build a new
Ruby on Rails 7 app, I aim to create something as modern and reactive as a
Single Page Application (SPA) written in React. The project I'm currently working on, named
AVIMBU, can be explored further
here.
A common feature in SPAs is the use of modals. My goal was to find a simple yet powerful and flexible approach to integrate Bootstrap modals without adding them permanently to the DOM, only revealing them upon a button click. Fortunately,
Turbo Frames offer a solution to transfer HTML over the wire, allowing us to display modals without rerendering the entire page.
This blog post provides a step-by-step guide on integrating a
Bootstrap 5 modal in a Ruby on Rails 7 application using Turbo Frames, thus enhancing your application's interactivity without full page reloads.

boostratp_modal-2
Ruby On Rails#
Section 1: Initial Project Setup#
In order to create a barebones Rails 7 project with Bootstrap preconfigured, run the following command:
1rails new bootstrap_modal --css=bootstrap -j=esbuild
This command creates a Rails project with the latest version of Bootstrap preconfigured. For JavaScript packaging, we will use esbuild
, which is straightforward and simple to use. You could also opt for using webpacker
or the more modern import_maps
approach, however, for now it's easiest to stick with esbuild
due to its straightforward integration of Bootstrap.
After project creation, navigate to the project directory and start it with:
Your application should now be available at localhost:3000
.
This initial setup comes with Turbo enabled. Verify this by checking for the following line in app/javascript/application.js
:
1import "@hotwired/turbo-rails"
Section 2: Add a Turbo Frame Tag#
In order to achieve a reactive and modern-feeling web application, we'll utilize Turbo Frames. Turbo Frames, a key component of Hotwire, allow us to define which parts of an application's HTML should be replaced, rather than doing a full page reload.
To use them, add the following line to a view file:
erb
1<%= turbo_frame_tag "modal", target: "_top" %>
I added this line to my
application.html.erb
file, right above the
part. This allows for adding my modal HTML element to this frame. The
turbo_frame_tag
creates a
div
element with the class set to
modal
, which Turbo uses to replace the content of this div with the new Turbo Frame content. The
attribute allows for conventional redirects (e.g., on model creation).
Section 3: Add a Modal Partial#
From the Bootstrap documentation, you can find some skeleton Bootstrap 5 modal examples
here. I used one of them and created a
_modal.html.erb
partial for later reuse. Ensure to copy an "active" modal instead of a hidden one. The partial looks like this:
erb
1<%= turbo_frame_tag "modal" do %>
2 <div class="modal fade" tabindex="-1" data-controller="modal">
3 <div class="modal-dialog modal-dialog-centered modal-lg">
4 <div class="modal-content">
5 <div class="modal-header">
6 <div class= "modal-title fs-3">
7 <%= title %>
8 </div>
9 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
10 </div>
11 <div class="modal-body" id="remote_modal_body" >
12 <%= yield %>
13 </div>
14 </div>
15 </div>
16 </div>
17<% end %>
Section 4: Stimulus Controller for Background Handling#
Since the Bootstrap modal relies on some JavaScript logic, I added a few lines of JavaScript to ensure proper background handling. For this, I chose to use a Stimulus controller as it's the easiest solution for handling JavaScript within a modern Rails 7 application with Turbo. The controller looks like this:
javascript
1import { Controller } from "@hotwired/stimulus"
2import { Modal } from "bootstrap";
3
4// Connects to data-controller="modal"
5export default class extends Controller {
6 connect() {
7 let backdrop = document.querySelector(".modal-backdrop");
8 if (backdrop) {
9 backdrop.remove();
10 }
11 this.modal = new Modal(this.element);
12 this.modal.show();
13 this.element.addEventListener("hidden.bs.modal", (event) => {
14 this.element.remove();
15 });
16 }
17}
As seen in my _modal.html.erb
partial, I've added the data-controller="modal"
parameter to link this Stimulus controller with the modal HTML. This controller ensures that the modal backdrop is handled correctly by properly removing and showing the Modal element.
Section 5: Adapt Controller Actions to Use Turbo Frames#
Here's the crucial part of integrating Turbo Frames into our Controller actions. I created a simple example scaffold for some model logic:
1rails g scaffold project name description
This command creates all necessary controllers, models, and views to create, update, or delete Project models. Change your root route to the index action of the Project controller by adding this to routes.rb
:
ruby
1root "projects#index"
Now, when you open your application, you'll see the index page of the Project model, allowing you to create some Project models. However, the new and edit actions redirect to their respective .html.erb
pages. We want them to open within a modal. This can be done by adapting the view files as follows:
erb
1<%= render 'partials/modal', title: "New Project", fade_in: true do %>
2 <%= render "form", project: @project %>
3<% end %>
Do the same for the edit.html.erb
file. Now, when you try to create or edit a model, the view renders within a Bootstrap modal 🥳
Section 6: Add Form Error Handling#
If you add model validations, such as requiring a name, you might notice that errors are not properly handled within the modal.
ruby
1class Project < ApplicationRecord
2 validates :name, presence: true
3end
In order to improve this, we need to change how the controller responds in case of an error for both the create and update actions:
ruby
1# POST /projects or /projects.json
2def create
3 @project = Project.new(project_params)
4
5 if @project.save
6 redirect_to project_url(@project), notice: "Project was successfully created."
7 else
8 render :form_update, status: :unprocessable_entity
9 end
10end
11
12# PATCH/PUT /projects/1 or /projects/1.json
13def update
14 if @project.update(project_params)
15 redirect_to project_url(@project), notice: "Project was successfully updated."
16 else
17 render :form_update, status: :unprocessable_entity
18 end
19end
This adds a custom
render
response for error cases, instructing the controller to render the
form_update
view. This view will update the modal's body via a Turbo Stream (see
Turbo Streams documentation) to show the correct error and avoid a full page reload. The
form_update.turbo_stream.erb
file looks like this:
erb
1<%= turbo_stream.update "remote_modal_body" do %>
2 <%= render "form", project: @project %>
3<% end %>
Now, if you try to save a Project model without a name, the error is shown within the modal:

boostratp_modal-3
Modal with Error
That's it! We've successfully implemented a Bootstrap modal using Turbo Frames, making our Rails 7 application feel more responsive and modern.
Conclusion#
The final product should look like this:

boostratp_modal-4
Final Product
We've covered how to integrate a Bootstrap 5 modal in a Ruby on Rails 7 application using Turbo Frames. This integration allows for a more dynamic and responsive user interface without the need for full page reloads. Experiment with Turbo Frames and Bootstrap modals, and feel free to share your experiences or ask questions in the comments below.
If you want to know more about me, feel free to check out my
homepage or follow me on Twitter
here.

boostratp_modal-5
Bootstrap 5 — a developer friendly CSS component library
Additional Resources#
The code used in this tutorial can be found
here.
Alternative resources: