Learn Hotwire

How to Install Bootstrap in Rails 7

Sep 27, 2022 - 5 min read
How to Install Bootstrap in Rails 7

With the introduction of the new asset pipeline, many new tools are available for you to use.

But if you want to know how to install Bootstrap in Ruby on Rails 7 and save some time building your apps, you've come to the right place.

There are at least two ways in which you can install Bootstrap.

Unfortunately, the second option (as you'll see in a minute) doesn't work out of the box.

If you want the video version of this post, here it is.

Option #1: New project

If you're starting from scratch, you're in luck because the way to install Bootstrap and Javascript bundling in a new Rails 7 application is super easy.

You can just use the -j esbuild --css bootstrap flags, and you're done. And this approach works perfectly out of the box.

It'll install both the cssbundling-rails gem and the jsbundling-rails gem for you, generating the necessary configuration.

With this option you've got both the CSS and the Javascript part (using esbuild) working.

Option #2: Existing project

But if your project was started with import maps (the default in Rails 7), and you want to migrate to Bootstrap and a Javascript bundler (e.g., esbuild), well... the setup is not that straightforward.

The first thing you'll need to do is to install the cssbundling-rails gem and then use the installer that the gem provides to generate the necessary configuration.

bundle add cssbundling-rails
./bin/rails css:install:bootstrap

Here's what the installer does.

  1. It creates the builds folder, and links it in the manifest file.
  2. It removes the application.css file because it generates its own.
  3. It adds a package.json file to store Javascript dependencies.
  4. It installs the foreman gem, and it generates a config file for it.
  5. It adds a bin/dev script to start your rails server, and watch for any changes to your CSS files.
  6. It creates the Bootstrap-specific scss file, which will be bundled into an application.css file.
  7. It installs all the Javascript dependencies listed in the package.json file.
  8. It appends the Bootstrap font path to the assets paths.
  9. It adds the Bootstrap Javascript import to the application.js file.
  10. And finally, it configures the build:css command and runs it to build the application.css file.

So if we try to use a Bootstrap component, like the Navbar, you'll see it looks pretty good.

Bootstrap Navbar component

But there's a problem. The drop-down doesn't work.

And that's because we don't have Javascript bundling set up yet.

So let's install the jsbundling-rails gem, and add the esbuild bundler by running the installer that the jsbundling-rails gem provides.

bundle add jsbundling-rails
./bin/rails javascript:install:esbuild

Here's what the installer does:

  1. It checks for a builds folder, but because we already have it, it doesn't do anything.
  2. It adds the javascript_include_tag to the application layout file.
  3. It adds a task in the foreman config file, to watch for any Javascript changes.
  4. It installs the esbuild bundler, and it tries to build the Javascript code.

And that's where it gets into trouble.

$ esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets
✘ [ERROR] Could not resolve "controllers"

      3import "controllers"
        │        ~~~~~~~~~~~~~
        ╵        "./controllers"

  Use the relative path "./controllers" to reference the file "app/javascript/controllers/index.js".
  Without the leading "./", the path "controllers" is being interpreted as a package path instead.

1 error
    throw err;

Error: Command failed: /Users/cezar/Work/ror/bootstrap/node_modules/esbuild-darwin-arm64/bin/esbuild app/javascript/application.js --bundle --sourcemap --outdir=app/assets/builds --public-path=assets
    at checkExecSyncError (node:child_process:828:11)
    at Object.execFileSync (node:child_process:863:15)
    at Object.<anonymous> (/Users/cezar/Work/ror/bootstrap/node_modules/esbuild/bin/esbuild:209:28)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47 {
  status: 1,
  signal: null,
  output: [ null, null, null ],
  pid: 94418,
  stdout: null,
  stderr: null

Node.js v17.8.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Because we have some code left over from import maps, which conflicts with how the jsbundling-rails gem works.

So let's fix these problems.

Fixing the installation

The first thing to do is to install the turbo-rails and stimulus packages.

yarn add @hotwired/turbo-rails
yarn add @hotwired/stimulus

Then, I'll adjust the import path in the application.js file, and I'll remove the old stimulus imports.

--- a/app/javascript/application.js
+++ b/app/javascript/application.js
-import "controllers"
+import "./controllers";

--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
-import { application } from "controllers/application"
-// Eager load all controllers defined in the import map under controllers/**/*_controller
-import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
-eagerLoadControllersFrom("controllers", application)
-// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
-// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
-// lazyLoadControllersFrom("controllers", application)
+import { application } from "./application";

In the application layout file, I'll remove the javascript_importmap_tags helper since it's no longer required.

--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
     <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
-    <%= javascript_importmap_tags %>
     <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>

And finally, I'll unlink the other javascript folders leaving just the builds folder and the images folder in the manifest.

--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
 //= link_tree ../images
-//= link_tree ../../javascript .js
-//= link_tree ../../../vendor/javascript .js
 //= link_tree ../builds

So now, if we take a look at the Navbar in the browser, you'll see it looks the same but this time the drop-downs do work.

Dropdown working

If you want to learn more about how the asset pipeline works, you can check out the Quick and Easy Guide to the Asset Pipeline in Rails 7, where I share an overview of the different tools that the new asset pipeline provides.


Every other week you'll get at least 1 actionable tip on how to become a better Ruby on Rails developer.
    We won't send you spam. Unsubscribe at any time.
    Built with ConvertKit
    Cezar Halmagean
    Software development consultant with over a decade of experience in helping growing companies scale large Ruby on Rails applications. Has written about the process of building Ruby on Rails applications in RubyWeekly, SemaphoreCI, and Foundr.