Which Bootstrap install works in Rails comes down to the asset pipeline. A new app can use the rails new –css bootstrap flag. An existing one can take cssbundling-rails, or the bootstrap gem paired with a Sass engine. A CDN link is there for quick tests.
Rails 8 apps run Propshaft, and Propshaft compiles no Sass. Whatever route you pick has to bring its own compiler, either Dart Sass through Node or a standalone Dart Sass executable.
Webpacker, the bundler that Rails 5 and 6 tutorials assume, is retired. The rails/webpacker README points to jsbundling-rails as the first replacement, so older guides need some translating before they work.
Which method fits your Rails version and asset pipeline?
Start with the pipeline. Rails 8 ships Propshaft, which pairs with cssbundling-rails or with the bootstrap gem plus dartsass-rails. Rails 7 apps still on Sprockets can keep the gem route with dartsass-sprockets. The routes differ mostly in who compiles the Sass and whether Node has to be installed.
| Route | Pipeline | Sass compiler | Node required |
|---|---|---|---|
| rails new –css bootstrap | Propshaft (Rails 8) or Sprockets (Rails 7) | Dart Sass through cssbundling-rails | Yes |
| cssbundling-rails on an existing app | Propshaft or Sprockets | Dart Sass (npm sass package) | Yes |
| bootstrap gem + dartsass-rails | Propshaft | Standalone Dart Sass | No |
| bootstrap gem + dartsass-sprockets | Sprockets | Dart Sass | No |
| CDN link | Any | None, precompiled CSS | No |
If there’s no Node on the machine, go with the bootstrap gem and dartsass-rails, and skip the optional Autoprefixer step, since that needs a JavaScript runtime. If Node already builds your JavaScript, cssbundling-rails is the natural fit. A Rails 7 app that’s staying on Sprockets uses the bootstrap gem with dartsass-sprockets. For a throwaway prototype the CDN link is fine, as long as you don’t need Sass variable overrides.
For an importmap app, the gem with dartsass-rails has the fewest moving parts. The cssbundling-rails README frames the choice the same way.
A few numbers worth having on hand. The latest stable Bootstrap is 5.3.8 (Versio.io, September 2026), and the gem README constrains the version with ~> 5.3.8 (twbs/bootstrap-rubygem). Propshaft has been the default pipeline since Rails 8 (rails/rails pull request 51799). On the Sprockets side, dartsass-sprockets needs Ruby 2.6+ and Rails 5+ (bootstrap-rubygem README). The Node route needs Node and Yarn installed, plus npx 7.1.0 or later (cssbundling-rails README).
How to add Bootstrap to a new Rails app with the –css flag
Run rails new myapp –css bootstrap. The generator installs cssbundling-rails and sets up a Bootstrap stylesheet build.
The flag works on Rails 7 and later, per the cssbundling-rails README.
The generator leaves a handful of pieces behind:
- cssbundling-rails in the Gemfile
- a Sass entry file at app/assets/stylesheets/application.bootstrap.scss
- compiled output in app/assets/builds/application.css, added to .gitignore
- bin/dev, which starts the Rails server and the CSS watcher together
- Check that Node and Yarn are installed.
- Run rails new myapp –css bootstrap.
- Move into myapp and run yarn install if node_modules is missing.
- Start the app with bin/dev.
- Look for app/assets/builds/application.css after the first build.
The CSS build runs through Node, so a machine without Node and Yarn cannot build the app. Those developers should take the bootstrap gem route instead.
Adding Bootstrap to an existing app with cssbundling-rails
It takes two commands on a running app: bundle add cssbundling-rails, then bin/rails css:install:bootstrap. The installer sets up a Node-based Sass build that writes to app/assets/builds/application.css.
The README recommends this gem when Node already processes your JavaScript. Without Node, it points to dartsass-rails.
Installing the gem and running the installer
- Check that Node and Yarn are installed.
- Run ./bin/bundle add cssbundling-rails.
- Run ./bin/rails css:install:bootstrap.
- Keep stylesheet_link_tag “application” in the layout. The build output takes over from the default application.css.
- Start the app with bin/dev.
In production, the css:build task attaches to assets:precompile and runs yarn build:css. It also attaches to test:prepare, so a test setup that skips that task must call css:build itself.
Removing sassc-rails and sass-rails
A SassC::SyntaxError during assets:precompile means the legacy SassC engine can’t parse newer CSS features. Run bundle remove sass-rails (or sassc-rails) and try again.
If production complains that “application.css not in asset pipeline”, app/assets/builds is probably missing from the repository. Add the directory back with a .keep file.
The odd one is “Function rgb is missing argument $green”. It usually means a legacy sassc-rails entry is still sitting in Gemfile.lock, often as a transitive dependency. Set Rails.application.config.assets.css_compressor = nil in config/initializers/assets.rb for every environment, not only production.
Using the bootstrap gem with dartsass-rails or Sprockets
The bootstrap gem delivers Bootstrap 5 Sass and JavaScript as Rails assets, and it needs exactly one Sass engine in the Gemfile. Add gem ‘bootstrap’, ‘~> 5.3.8’, then pair it with dartsass-rails on Propshaft or dartsass-sprockets on Sprockets.
The gem README says it works with importmaps or Sprockets, but not Webpack. A jsbundling-rails app should take the npm package through cssbundling-rails instead.
Avoid sassc-rails on new apps. The README marks it deprecated.
Gem route on Propshaft
- Add gem ‘bootstrap’, ‘~> 5.3.8’ and gem ‘dartsass-rails’ to the Gemfile
- Run bundle install, then bin/rails dartsass:install
- Put @import “bootstrap”; in app/assets/stylesheets/application.scss, below any custom variables
- Start everything with bin/dev
The installer creates application.scss, and dartsass:build writes the result to app/assets/builds/application.css. Asset paths count as Sass load paths, so the gem’s stylesheets resolve without extra configuration (dartsass-rails README).
Expect deprecation warnings. Newer Dart Sass releases flag the @import rule that Bootstrap 5.3 still uses, and the Bootstrap docs say the warnings do not block compilation.
If the noise bothers you, dartsass-rails passes build options from config/initializers/dartsass.rb, including –quiet-deps. For older browsers the gem README suggests Autoprefixer through autoprefixer-rails, which needs a JavaScript runtime such as Node, so a Node-free setup leaves it out.
Gem route on Sprockets
Sprockets apps add dartsass-sprockets next to the gem and keep the rest of the pipeline as is. The stylesheet file is where people get caught.
Rename application.css to application.scss, delete every *= require and *= require_tree line, and pull stylesheets in with @import. Keep sprockets-rails at v2.3.2 or newer. Don’t use *= require inside Sass, because other stylesheets then lose access to Bootstrap’s mixins and variables, as the gem README warns.
Switching pipelines later is a migration, not a toggle. Propshaft and Sprockets cannot run in parallel because their namespaces collide, according to a Rails discussion forum thread about moving a Bootstrap app to Rails 8.
Loading Bootstrap JavaScript with importmap, esbuild and Turbo
Bootstrap JavaScript loads through an importmap pin, an esbuild import or Sprockets directives, and Popper has to load with it. Dropdowns, popovers and tooltips depend on Popper for positioning, so a missing Popper breaks all of them.
Most plugins, including dropdown menus, run from data attributes alone. Tooltips and popovers are the exceptions.
Sprockets apps keep the older directives in application.js: //= require popper, then //= require bootstrap-sprockets. jQuery stays optional, since Bootstrap 5 dropped the requirement.
Importmap setup
The gem README gives two pins, and the files behind them also need to be listed for precompiling.
- pin “bootstrap”, to: “bootstrap.min.js”, preload: true
- pin “@popperjs/core”, to: “popper.js”, preload: true
- Add both files to config.assets.precompile in config/initializers/assets.rb, which the README requires for pinned files
Apps that prefer downloaded files can run ./bin/importmap pin bootstrap. It saves the package into vendor/javascript through JSPM, so open config/importmap.rb afterward and confirm @popperjs/core is pinned too.
Skipping the Popper pin produces a console error: Failed to resolve module specifier “@popperjs/core”. The Bootstrap docs describe that exact failure for ES modules in the browser.
esbuild setup
Apps on jsbundling-rails install both packages with yarn add bootstrap @popperjs/core. The Bootstrap docs call the dist files UMD ready for bundlers.
Add import * as bootstrap from “bootstrap” to app/javascript/application.js. If views or inline scripts call Bootstrap directly, you also need window.bootstrap = bootstrap (Saeloun, 2026). And if you’d rather use a script tag, bootstrap.bundle.min.js carries Popper inside.
Stimulus initialization with Turbo
A tooltip or popover stays inert until JavaScript creates it. Turbo page visits also swap the body without a full reload, which makes the usual page-load approach unreliable.
Bootstrap’s documentation states that tooltips are opt-in for performance reasons, so the developer must initialize them.
- Put data-controller=”tooltip”, data-bs-toggle=”tooltip” and data-bs-title on each trigger
- In connect(), call bootstrap.Tooltip.getOrCreateInstance(this.element)
- In disconnect(), call dispose() on that instance, because tooltips must be hidden before their element leaves the DOM
A DOMContentLoaded listener fires once per full page load, so Turbo visits skip it. A Stimulus controller runs whenever its element enters the page.
How to customize Bootstrap variables and import only the components you need
Set variable overrides before Bootstrap’s own variables load, then import only the partials the app uses. Every Bootstrap Sass variable carries a !default flag, so a value you assign first wins.
Order matters in both directions. Overrides go above the variables import, while your own rules that read Bootstrap variables go below it, or Sass raises an undefined variable error such as $primary (Saeloun, 2026).
On the gem route the overrides live in app/assets/stylesheets/application.scss, above @import “bootstrap”. With cssbundling-rails they go in app/assets/stylesheets/application.bootstrap.scss, and you import from bootstrap/scss/ because node_modules sits on the load path. Either way, a small partial such as _custom-variables.scss, imported first, keeps things tidy.
A full import has one limit. Overrides placed above it cannot call Bootstrap functions such as tint-color(), because the functions have not loaded yet (Bootstrap docs).
Partial imports fix that, and they follow the order from the Bootstrap docs:
- functions
- your variable overrides
- variables and variables-dark
- maps, mixins and root
- the component partials you use, such as utilities, reboot, type, containers, grid and helpers
- utilities/api, last
The gem route skips that list. Copy the gem’s _bootstrap.scss into your assets as _bootstrap-custom.scss, comment out the Bootstrap components the app never renders, and import bootstrap-custom instead of bootstrap.
Be careful with the maps, though. They have required keys, and removing primary, success or danger from $theme-colors can break compilation, because links, buttons and form states read them (Bootstrap docs).
Dart Sass deprecated @import in version 1.80.0, and removal waits for Dart Sass 3.0.0, which arrives no sooner than two years after 1.80.0 (Sass documentation). Bootstrap 5.3 still loads through @import, so your custom file compiles today with warnings.
In terse mode the compiler prints each deprecation type five times, then goes quiet. The –quiet-deps flag silences warnings from any stylesheet found on a load path, so Bootstrap’s partials stop complaining while your own files still do.
Sass variables suit theme-wide changes. A tweak to one component is simpler as a plain CSS rule, which this guide to overriding Bootstrap CSS walks through.
Testing the setup with a Bootstrap layout in application.html.erb
A navbar and a grid in application.html.erb prove that both the stylesheet and the JavaScript load. Before that, the layout head needs the HTML5 doctype, the viewport meta tag, the stylesheet tag and the JavaScript tag for your route.
Bootstrap treats the doctype as a requirement, since a missing one produces incomplete styling. The viewport tag (width=device-width, initial-scale=1) gives correct rendering and touch zoom on phones, and Rails’ generated layout already carries both.
- Keep stylesheet_link_tag “application” and your JavaScript tag in the head.
- Create app/views/shared/_navbar.html.erb with a nav element carrying navbar and navbar-expand-lg.
- Add a brand link and a toggler button with data-bs-toggle=”collapse”, plus a data-bs-target that matches the id of the collapsing div.
- Fill that div with a navbar-nav list and mark the current page with aria-current=”page”.
- Render the partial above yield in application.html.erb.
- Wrap the page body in container, row and col classes.
The navbar component collapses behind its toggler below the lg breakpoint, which starts at 992px, so shrink the window under that width to test it (Bootstrap documentation).
Containers, rows and columns sit on a twelve-column grid system with six breakpoints, from xs to xxl.
When something is off, the symptoms are easy to read. A missing stylesheet gives you plain browser-default HTML, and the network panel shows a failed request for the application stylesheet. If the JavaScript is missing, the styling looks right but the toggler and the dropdown do nothing on click. Popper alone being absent breaks the dropdown while the toggler still works, since the docs list dropdowns, popovers and tooltips as the Popper dependents.
Why Bootstrap stops working in Rails and how to fix it
Most failed installs trace back to a missing Sass engine in the Gemfile, a missing application.css build, stale files in public/assets, or Sass that nothing builds in CI. The table pairs each symptom with its fix.
| Symptom | Likely cause | Fix |
|---|---|---|
| “bootstrap-rubygem requires a Sass engine” | Gem installed with no engine in the Gemfile | Add dartsass-rails (Propshaft) or dartsass-sprockets (Sprockets) |
| Propshaft::MissingAssetError for application.css | No built file in app/assets/builds, or the watcher never ran | Start with bin/dev, then confirm the file exists |
| Old styles after edits | Locally precompiled files in public/assets served first | Run rails assets:clobber |
| No styles in CI browser tests | Sass never built before the tests | Add bundle exec rails dartsass:build to the pipeline |
Sources don’t fully agree on the –css flag. The cssbundling-rails README presents rails new –css bootstrap as a ready setup on Rails 7 and later.
Users on r/rails report mixed results with that flag on Rails 8. One commenter described a working app in December 2024, another saw identical files and still no Bootstrap in January 2025, and a Rails 8.0.1 user hit a Propshaft::MissingAssetError for application.css in February 2025.
The thread names no cause. Treat the flag as a starting point and check app/assets/builds/application.css before debugging anything else.
Moving Bootstrap off Webpacker and sassc-rails
Swap Webpacker for jsbundling-rails or an importmap, swap sassc-rails for dartsass-rails or cssbundling-rails, and move the CSS first. The rails/webpacker README calls the gem retired and names jsbundling-rails as the first replacement.
A DEV Community migration guide advises against jumping straight to importmap-rails, since that surfaces more problems at once.
The code changes are small but easy to miss:
- @import “~bootstrap/scss/bootstrap” loses its tilde and becomes @import “bootstrap/scss/bootstrap”, with node_modules on the load path
- yarn add popper.js gives way to @popperjs/core
- import “bootstrap” moves from packs/application.js to app/javascript/application.js
- the bootstrap gem can go once Bootstrap comes from npm
Leftover Sass lines in app/assets/config/manifest.js make Sprockets try to build Sass itself. After sassc-rails is gone, that raises LoadError: cannot load such file — sassc, and removing those lines fixes it (dartsass-rails README).
Propshaft compiles no Sass at all, so an app that leaned on Sprockets for it needs one of two tools.
cssbundling-rails keeps the npm bootstrap package and the Node scripts a Webpacker app already runs, and Sass options live in the build:css script in package.json. The cost is that Node and Yarn stay required in CI and on deploy, and development gets one more build step.
dartsass-rails drops Node for CSS entirely, and the cssbundling-rails README calls it simpler for importmap apps. The catch is that Bootstrap JavaScript has to arrive through an importmap pin or the gem. Also, only app/assets/stylesheets/application.scss builds until you configure more entry points.
Skip the move entirely when the app stays on Sprockets. The gem route keeps working there.
Staying on Webpacker also works, but the README promises Ruby-side security fixes only and no JavaScript library updates.
FAQ About Adding Bootstrap to Ruby on Rails
Does Bootstrap 5 need jQuery in Rails?
No. Bootstrap 5 runs without it, and the gem README lists jQuery as optional.
One quirk applies when jQuery is on the page. Bootstrap then emits its events through jQuery, so listeners need jQuery’s .on instead of addEventListener, unless the body carries data-bs-no-jquery (Bootstrap docs).
Should a Rails app use Bootstrap or Tailwind?
Pick Bootstrap for ready-made components such as the navbar and modal, and Tailwind for composing designs from utility classes. The Rails generator accepts both, as –css bootstrap and –css tailwind.
This Tailwind vs Bootstrap comparison covers the trade-offs in detail.
How do I style Rails form helpers with Bootstrap classes?
Pass the classes through each helper’s class option: form-label on labels, form-control on text fields, and btn btn-primary on the submit button.
Rails renders them as plain HTML attributes, so no extra gem is needed. Layout options are laid out in this guide to the Bootstrap form markup.
How do I add Bootstrap Icons to a Rails app?
Bootstrap Icons ships as its own package, so install bootstrap-icons with Yarn and import its stylesheet in the Sass entry file.
The icon font files must also be reachable by Propshaft. Saeloun documents the same webfont path fix for Font Awesome (2026).
How do I deploy a Rails app that uses Bootstrap?
Run rails assets:precompile during the deploy. dartsass-rails attaches dartsass:build to that task, and cssbundling-rails does the same with css:build.
Propshaft then copies the compiled files into public/assets with digest hashes (Propshaft README).
How do I remove Bootstrap from a Rails app?
Remove it in reverse order of the install: delete the bootstrap gem or npm package, the @import line, and the JavaScript import or importmap pins. Then strip the Bootstrap classes from views.
Finish with rails assets:clobber, so old compiled CSS in public/assets stops overriding the new build.
Keeping Bootstrap Maintainable Through Rails Upgrades
Once Bootstrap is in, you own the Bootstrap version, the Sass engine and the asset pipeline. Only the Sass engine has a published removal timeline for the syntax Bootstrap 5.3 uses.
Bootstrap 5.3 loads through @import, and Dart Sass removes that rule no earlier than version 3.0.0. Existing custom Sass keeps compiling until that major release, which is tied to neither Rails nor Bootstrap.
Lock the bootstrap and Sass engine versions first, because a locked version makes the later checks repeatable. After that, keep your overrides in one partial and build the stylesheet in CI.
The Node-free gem route carries one trade-off. Its README covers importmaps and Sprockets only, so a later move to a JavaScript bundler changes the install route.
Position verified October 2026. A stable Dart Sass 3.0.0, or a Bootstrap 6 release (work is under way on a v6-dev branch of twbs/bootstrap, while the main README still describes Bootstrap 5), would change this advice.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026

