Adding Bootstrap to Laravel comes down to installing bootstrap, @popperjs/core and sass with npm, importing them in resources/js/app.js, and loading that entry from the root Blade template with the @vite directive.

A fresh Laravel 13 app ships with Tailwind CSS 4 and Vite 8 instead of Bootstrap (laravel/laravel 13.x package.json, 2026). So Bootstrap goes in either next to Tailwind or in place of it.

Most people want it for the ready-made navbars and modals. The other draw is Sass. One file, a few variables, and the whole framework looks different.

Which method should you use to add Bootstrap to Laravel?

Use npm with Vite for almost every project. It handles Sass overrides and selective JavaScript imports, and the production build is versioned too. Nothing gets overwritten either, which is not true of the laravel/ui preset.

Laravel doesn’t include Bootstrap. According to the Laravel 13.x Vite documentation (2026), a fresh app ships with Tailwind and a preconfigured vite.config.js, so Bootstrap is either added on top or swapped in.

MethodBuild stepSass overridesWeak fit
npm with ViteYes, npm run dev and npm run buildYesMachines without a current Node.js
laravel/uiYes, same Vite pipelineYes, through the generated app.scssNew projects (package is labeled legacy)
CDN linksNoneNo, plain CSS overrides onlyThemed sites, trimmed bundles

npm with Vite fits new apps, themed interfaces, and anything that reaches production through a CI build. laravel/ui only makes sense if you want its Bootstrap login and registration views (php artisan ui bootstrap --auth). A CDN is fine for a quick prototype or a demo, and for servers where Node.js isn’t available at deploy time.

laravel/ui still installs on Laravel 13, because its 4.x line lists support for Laravel 9 through 13. The README calls the package legacy, though, and points new projects to Laravel Breeze.

If you’re not sure about dropping the Tailwind default, how Tailwind and Bootstrap compare is worth a read before you remove anything from an existing app.

Requirements for Bootstrap in Laravel

Laravel 13 needs PHP 8.3 or newer, and the Vite toolchain in a fresh app wants a recent Node.js. Check both before you touch package.json.

  • Laravel 13 runs on PHP 8.3 to 8.5, while Laravel 12 covers 8.2 to 8.5 (Laravel release notes, 2026).
  • Node.js has to be 20.19 or newer, or 22.12 or newer (Vite documentation, 2026).
  • A new Laravel 13 app uses Vite ^8.0.0 with laravel-vite-plugin ^3.1 (laravel/laravel 13.x package.json).
  • The latest Bootstrap release on npm is 5.3.8, and @popperjs/core ^2.11.8 comes along as a peer dependency (npm registry, October 2026).
  • Laravel 12 stopped getting bug fixes on August 13, 2026. Security fixes continue until February 24, 2027 (Laravel release notes, 2026).

The sources disagree on Node.js. The Laravel 13.x Vite page still tells readers to have Node.js 16 or newer, while laravel-vite-plugin 3.2.0 declares ^20.19.0 || >=22.12.0 in its engines field and peers with Vite 8.

Go with the stricter number. It’s the one the installed packages actually declare.

How to install Bootstrap in Laravel with npm and Vite

https://www.youtube.com/watch?v=1tepWjb6xUs

The steps below match the Laravel 13 skeleton. You’ll touch package.json, a new Sass file, app.js, vite.config.js and the root Blade template.

What is shaping UX design today?

Uncover the newest UX design statistics: user behavior, design trends, ROI data, and insights driving better digital experiences.

Check Them Out →
  1. Run npm install bootstrap @popperjs/core, then npm install --save-dev sass. Bootstrap’s own Vite guide lists Sass as a required extra and Popper as the dependency behind dropdowns, popovers and tooltips.
  2. Create resources/sass/app.scss with a single line, @import "bootstrap/scss/bootstrap";. Vite resolves the bare path from node_modules, and the current Bootstrap guide uses no tilde and no alias.
  3. Open resources/js/app.js and load the Sass file first, then Bootstrap’s JavaScript:
import '../sass/app.scss';
import * as bootstrap from 'bootstrap';
  1. In vite.config.js, point the input at the JavaScript file and add refresh: true if it’s missing. The Tailwind plugin from a fresh app can stay in the plugins array until you decide to remove it.
laravel({
input: ['resources/js/app.js'],
refresh: true,
})
  1. Put @vite('resources/js/app.js') inside the <head> of the root template. The CSS is imported through JavaScript, so that one entry loads both.
  2. composer run dev starts the Laravel and Vite development servers together, per the Laravel documentation. Running npm run dev next to php artisan serve does the same job.
  3. Add <button class="btn btn-primary">Test</button> to any view. A blue, styled button means the Sass pipeline works.

A button only proves the CSS. Dropping a Bootstrap navbar into the layout tests the JavaScript too, since its collapse toggle only works when the import loaded.

Check the layout head for the viewport meta tag while you’re there. Bootstrap’s mobile-first sizing depends on it, according to the Bootstrap introduction docs.

How Laravel loads Bootstrap through the @vite directive

The @vite directive checks whether the Vite dev server is running. If it is, assets come from the server with hot module replacement.

If it isn’t, the directive loads the versioned files that npm run build wrote to public/build.

Development mode

npm run dev starts the Vite server, and @vite injects the Vite client that powers hot module replacement. With refresh: true, saving a file under resources/views or routes triggers a full page reload. Nothing compiled is read in this mode, which is why a page can work locally and break on a server.

Stop the dev server without building and the page throws a manifest error instead.

Production build

The Laravel documentation says npm run build versions and bundles the assets for deployment. In build mode, @vite loads those compiled files, including any CSS imported from JavaScript.

Compiled files land in public/build (the laravel/ui README names public/build/assets for compiled CSS and JavaScript). The build directory and the manifest filename are configurable, and @vite accepts a build path as its second argument.

The default .gitignore excludes public/build, so each environment has to run the build or receive the folder from CI, as Sentry’s Laravel troubleshooting notes point out.

Development works without a build. Production doesn’t.

Bootstrap JavaScript and Popper setup

Only dropdowns, popovers and tooltips need Popper. Modals, collapse, carousels, offcanvas panels and toasts run on Bootstrap’s own JavaScript, according to the Bootstrap introduction docs.

The npm package lists @popperjs/core as a peer dependency, so install it beside Bootstrap. Bootstrap’s Vite guide then imports Popper automatically through the bootstrap import.

import * as bootstrap from 'bootstrap' loads every plugin and is the simplest start. To keep unused plugins out of the bundle, name the ones you need, as in import { Tooltip, Toast, Popover } from 'bootstrap'. A single plugin can come in on its own too, with import Alert from 'bootstrap/js/dist/alert'.

Module scope hides the bootstrap object from inline scripts in Blade views. If a view calls new bootstrap.Modal(...), add window.bootstrap = bootstrap; to app.js.

Plain dropdown menus start working from a data-bs-toggle="dropdown" attribute alone. Inside a navbar they skip Popper for positioning, so a navbar menu can work while a standalone one fails.

Bootstrap tooltips behave differently. They are opt-in for performance and need initialization in code, per the Bootstrap docs:

const triggers = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...triggers].map(el => new bootstrap.Tooltip(el));

When something doesn’t open, start with these checks.

  • A dropdown that renders but never opens usually means the JavaScript is missing, since the plugin is what toggles the menu.
  • Bootstrap’s JavaScript docs warn that it clashes with React, Vue and Angular, which can leave dropdowns stuck open, and they recommend React Bootstrap or ng-bootstrap there.
  • Including bootstrap.js and bootstrap.min.js together isn’t supported.

Customizing Bootstrap with Sass variables

Variable overrides must be declared before Bootstrap’s own variables load. Every Bootstrap variable carries the !default flag, so a value that already exists is never replaced.

The order of lines in resources/sass/app.scss therefore decides whether a theme change works at all.

Overriding variables

Put overrides above the Bootstrap import in resources/sass/app.scss:

$primary: #6f42c1;
$enable-shadows: true;

@import "bootstrap/scss/bootstrap";

Bootstrap’s Sass documentation flags the catch. Its functions aren’t available at that point, so an override can’t call helpers such as tint-color() unless you import the functions partial first.

A new $primary also changes the color contrast of any text placed on it. Bootstrap’s color-contrast() function returns light, dark or black text for a base color using the WCAG algorithm, which makes it the quickest check.

Maps follow the same rules. Removing the primary, success or danger keys from $theme-colors may cause compilation errors, since Bootstrap uses them for links, buttons and form states.

Importing only what you use

Bootstrap’s Sass docs encourage importing only the partials a project needs, in a fixed order:

  1. functions
  2. your variable overrides
  3. variables and variables-dark
  4. maps, mixins and root
  5. optional partials such as utilities, reboot, type, containers, grid and helpers
  6. the utilities API, last

Each partial uses the same path style as the full import, for example @import "bootstrap/scss/grid";. A dropped partial removes its classes from the compiled CSS, so the file shrinks.

Some components depend on others, according to the same docs. Remove one partial at a time and rebuild.

Sass deprecation warnings

Dart Sass 1.80.0 deprecated @import and global built-in functions, according to the Sass project’s October 17, 2024 announcement. Bootstrap 5 still uses both, so compiling the Sass entry prints deprecation warnings.

Removal is scheduled for Dart Sass 3.0.0, no sooner than two years after 1.80.0. The latest sass release on npm is 1.105.1 (npm registry, October 2026), so nothing has been removed yet.

The Sass project says to treat the deprecation as a signal to migrate sooner rather than later. Bootstrap sees it differently. The 5.3 docs say the warnings can be ignored, and a Bootstrap maintainer wrote in the issue tracker that Sass module support is coming in v6, with any v5 change being a mid-version breaking change.

The gap has a cause. Fixing it requires dropping Node Sass support, a maintainer explained, which fits Bootstrap’s own build still pinning sass 1.78.0 (npm registry, 2025).

Vite hands scss preprocessor options straight to Sass, according to its documentation:

export default defineConfig({
plugins: [
laravel({ input: ['resources/js/app.js'], refresh: true }),
],
css: {
preprocessorOptions: {
scss: {
silenceDeprecations: ['import', 'global-builtin', 'color-functions'],
},
},
},
});

Silencing defers the migration. It doesn’t replace it.

Adding Bootstrap with laravel/ui or a CDN instead

laravel/ui scaffolds the whole Bootstrap setup but overwrites files, while a CDN link skips the build and gives up Sass.

laravel/ui scaffolding

Two commands install the preset:

composer require laravel/ui
php artisan ui bootstrap

The 4.x preset source shows exactly what it touches:

FileWhat the preset does
package.jsonAdds bootstrap, @popperjs/core and sass to devDependencies
vite.config.jsOverwrites it with a stub that has no Tailwind plugin
resources/sass/app.scss and _variables.scssCreates them, replacing files of the same name
resources/js/bootstrap.jsOverwrites it with a stub importing Bootstrap and axios
node_modules and yarn.lockDeletes them, so run npm install afterwards

Against the Laravel 13 skeleton, two things don’t line up. The skeleton’s resources/js/app.js holds only a comment and the Bootstrap preset doesn’t edit it, so nothing imports the generated bootstrap.js. The stub also imports axios, which the skeleton’s package.json doesn’t list.

Fix both by adding import './bootstrap'; to app.js and running npm install axios, or by deleting the axios lines from the stub.

CDN links without a build step

A CDN needs one stylesheet tag in the head and one script tag before the closing body tag. Pin an exact version in both URLs:

https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css
https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js

The bundle file includes Popper, so dropdowns, popovers and tooltips work without a second script. Bootstrap’s docs call for an integrity hash and crossorigin="anonymous" on each tag, and the hash changes with every version. Where jsDelivr is blocked, the docs point to cdnjs or unpkg, comparing hashes of the same length.

The cost is customization. Without Sass, the only route is to override Bootstrap’s default styles with custom CSS, and every unused component still downloads.

Replacing Tailwind or Laravel Mix with Bootstrap in an existing project

A Vite config replacing Laravel Mix

Remove the old pipeline first, then add Bootstrap. A Tailwind app needs its plugin, packages and CSS entry deleted, and a Mix app needs Vite before anything else.

Starting from Tailwind, the cleanup goes like this.

  • Uninstall tailwindcss and @tailwindcss/vite, both devDependencies in the Laravel 13 skeleton.
  • Delete the tailwindcss() call and its import from vite.config.js.
  • Remove @import 'tailwindcss'; from resources/css/app.css, or delete the file and its input entry.
  • Rewrite the Tailwind utility classes in your Blade views, since they stop rendering once the plugin is gone.

Most structural markup, such as cards, navbars, modals and forms, has a direct counterpart among Bootstrap components. Convert views one component at a time and keep the build green between steps.

Pagination links keep their Tailwind classes, because Laravel’s default pagination views are built for Tailwind (Laravel 13.x documentation). They look broken until the paginator points at Bootstrap’s views.

Coming from Laravel Mix, the Laravel Vite plugin upgrade guide sets laravel/framework 9.19.0 as the minimum, and the changes map one to one.

Laravel MixVite
webpack.mix.jsvite.config.js with laravel-vite-plugin
mix(‘css/app.css’) in Blade@vite directive in the root template
npm run productionnpm run build
MIX_ environment variablesVITE_ variables through import.meta.env
~bootstrap/scss/bootstrapbootstrap/scss/bootstrap

Tutorials quote 9.23 for new projects. The guide’s 9.19.0 is the number that matters for an existing app.

Mix-era tutorials also install jquery and popper.js. Bootstrap 5 needs neither.

Why is Bootstrap not working in Laravel?

Almost every failure traces back to how Laravel finds the built files, not to Bootstrap itself. Find your message in the table below.

SymptomCauseFix
Vite manifest not found at the public/build/manifest.json pathNo dev server running and no build outputRun npm run dev, or run npm run build and deploy public/build
Unable to locate file in Vite manifest: resources/sass/app.scssThe entry named in @vite was never builtAdd it to the input array in vite.config.js, then rebuild
Assets requested from a dead local dev server URLA leftover public/hot file makes @vite assume the dev server is runningDelete public/hot, or restart npm run dev
Sass cannot find a Bootstrap fileWrong import path, or Bootstrap not installedUse bootstrap/scss/bootstrap and run npm install
Old file names requested after a deployA long-running worker still holds the previous manifestRun php artisan reload, or restart the worker

The first three rows come from the Vite class in the Laravel 13.x framework source. It looks for the manifest inside the build directory (public/build by default) and treats the mere existence of the hot file as proof that the dev server is running.

In production, an uncaught manifest exception surfaces as a 500 internal server error. That’s why a missing build shows a generic error page instead of unstyled HTML.

The same class caches the parsed manifest in a static property, so a long-running worker such as Octane keeps the old one until it reloads. Laravel’s deployment documentation tells you to reload those services after every deploy.

If tags still look stale with a correct pipeline, php artisan view:clear is the troubleshooting step in the Laravel Vite plugin upgrade guide.

Bootstrap’s CSS and JavaScript are plain static files. The pipeline around them is what breaks.

Bootstrap in Laravel FAQ

Can Bootstrap and Tailwind run together in one Laravel project?

Yes, but isolate Tailwind first. Its Preflight base styles strip margins, heading styles and list bullets, which overlaps with Bootstrap’s own base styles.

Import only tailwindcss/theme.css and tailwindcss/utilities.css to skip Preflight, and add the prefix(tw) option to both imports, per the Tailwind documentation.

How do I make Laravel pagination use Bootstrap styling?

Call Paginator::useBootstrapFive() inside the boot method of AppServiceProvider.

Laravel’s default pagination views target Tailwind, according to the 13.x documentation. The same page lists useBootstrapFour() for Bootstrap 4 projects.

Is Composer a valid alternative to npm for installing Bootstrap?

Yes, because Bootstrap publishes twbs/bootstrap on Packagist. The files land in vendor/, though, while Bootstrap’s Vite guide installs from npm and its import paths assume node_modules.

Use npm for the Vite pipeline and keep Composer for PHP packages such as laravel/ui.

How do I add Bootstrap Icons to a Laravel project?

Run npm install bootstrap-icons, then import the font stylesheet in resources/sass/app.scss with @import "bootstrap-icons/font/bootstrap-icons";.

Icons render through markup such as <i class="bi bi-alarm"></i>. If Sass can’t resolve the font URLs, set $bootstrap-icons-font-dir first, per the Bootstrap Icons docs.

Browse the full set of Bootstrap icons for class names.

Should I use Bootstrap 6 in a Laravel project?

Not for an app you ship. npm publishes 6.0.0-alpha.1 under the next tag only, so npm install bootstrap still resolves to the 5.x line (npm registry, October 2026).

Alpha builds can change between releases, which makes the 5.3 line the safer pin.

Will Bootstrap 5 still work with jQuery on the page?

Yes. Bootstrap 5 doesn’t require jQuery, but it detects jQuery on the window object and registers its plugins there.

Events then fire through jQuery, so listeners must use .on instead of addEventListener, unless the body carries the data-bs-no-jquery attribute.

Does resources/js/bootstrap.js have anything to do with Bootstrap?

No. The name is a coincidence. In older Laravel apps the file held setup code for axios and Echo, and the Laravel 13 skeleton no longer ships it.

The version laravel/ui generates is the exception, since it also imports the Bootstrap package.

Keeping Bootstrap in Laravel Stable Through Updates

Bootstrap in Laravel stays stable when you upgrade Node.js first, then Vite together with laravel-vite-plugin, and sass and Bootstrap last, with a rebuild after each step. A failed build then points at the layer that changed.

The order follows the stack. Vite, laravel-vite-plugin and sass each declare a Node.js floor, and one upgrade to 20.19 or 22.12 satisfies all of them.

Staying on Bootstrap 5.3 buys predictable builds and costs a later move to Sass modules.

The sequence holds while sass stays on 1.x, as verified on October 11, 2026. A sass 3.0.0 release ends it, because Bootstrap 5 still compiles through @import.

With the build settled, the first screen worth building is often a login form built with Bootstrap.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)