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.
| Method | Build step | Sass overrides | Weak fit |
|---|---|---|---|
| npm with Vite | Yes, npm run dev and npm run build | Yes | Machines without a current Node.js |
| laravel/ui | Yes, same Vite pipeline | Yes, through the generated app.scss | New projects (package is labeled legacy) |
| CDN links | None | No, plain CSS overrides only | Themed 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.
- Run
npm install bootstrap @popperjs/core, thennpm 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. - Create
resources/sass/app.scsswith 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. - Open
resources/js/app.jsand load the Sass file first, then Bootstrap’s JavaScript:
import '../sass/app.scss';
import * as bootstrap from 'bootstrap';- In
vite.config.js, point the input at the JavaScript file and addrefresh: trueif 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,
})- 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. composer run devstarts the Laravel and Vite development servers together, per the Laravel documentation. Runningnpm run devnext tophp artisan servedoes the same job.- 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:
- functions
- your variable overrides
- variables and variables-dark
- maps, mixins and root
- optional partials such as utilities, reboot, type, containers, grid and helpers
- 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 bootstrapThe 4.x preset source shows exactly what it touches:
| File | What the preset does |
|---|---|
| package.json | Adds bootstrap, @popperjs/core and sass to devDependencies |
| vite.config.js | Overwrites it with a stub that has no Tailwind plugin |
| resources/sass/app.scss and _variables.scss | Creates them, replacing files of the same name |
| resources/js/bootstrap.js | Overwrites it with a stub importing Bootstrap and axios |
| node_modules and yarn.lock | Deletes 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.jsThe 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

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 Mix | Vite |
|---|---|
| webpack.mix.js | vite.config.js with laravel-vite-plugin |
| mix(‘css/app.css’) in Blade | @vite directive in the root template |
| npm run production | npm run build |
| MIX_ environment variables | VITE_ variables through import.meta.env |
| ~bootstrap/scss/bootstrap | bootstrap/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.
| Symptom | Cause | Fix |
|---|---|---|
| Vite manifest not found at the public/build/manifest.json path | No dev server running and no build output | Run npm run dev, or run npm run build and deploy public/build |
| Unable to locate file in Vite manifest: resources/sass/app.scss | The entry named in @vite was never built | Add it to the input array in vite.config.js, then rebuild |
| Assets requested from a dead local dev server URL | A leftover public/hot file makes @vite assume the dev server is running | Delete public/hot, or restart npm run dev |
| Sass cannot find a Bootstrap file | Wrong import path, or Bootstrap not installed | Use bootstrap/scss/bootstrap and run npm install |
| Old file names requested after a deploy | A long-running worker still holds the previous manifest | Run 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.
- 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

