---
name: bootstrap-v4-v6-migration
description: Migrate a project from Bootstrap 4 to Bootstrap 6. Use when upgrading from Bootstrap 4, jumping two major versions, or updating v4 class names, data attributes, forms, and JavaScript across the v4-to-v5 and v5-to-v6 steps.
guide: /guides/migration
---
# Bootstrap v4 to v6 Migration
There is no direct v4 to v6 path. Migrate in two stages — v4 to v5 first (this skill), then v5 to v6 (the `bootstrap-v5-v6-migration` skill). Skipping the middle stage doesn't work, because many v4 names were renamed in v5 and renamed *again* in v6: `.text-primary` becomes `.fg-primary` only after passing through v5, and `.badge-primary` becomes `.badge.theme-primary` by way of `.badge.bg-primary`.
## Workflow
Work through each step in order. After each step, search the codebase for remaining v4 patterns before moving on.
- [ ] Step 1: Plan the two-stage migration
- [ ] Step 2: v4 to v5 — dependencies, build, and Sass
- [ ] Step 3: v4 to v5 — JavaScript
- [ ] Step 4: v4 to v5 — classes and attributes
- [ ] Step 5: v5 to v6 — hand off to the v6 migration skill
- [ ] Step 6: Verify
---
## Step 1: Plan the two-stage migration
1. Get the project onto Bootstrap 5 and **working** — build passing, pages rendering, tests green — before touching v6. Landing the v5 stage as its own commit (or PR) keeps the two rename waves separable when something breaks.
2. Inventory what you're dealing with first. The v4 features that cost the most time are custom forms, `.input-group-append` / `.input-group-prepend`, jumbotrons, `.media` objects, `.card-deck` / `.card-columns`, and any jQuery that calls Bootstrap plugins.
3. Decide about jQuery separately. Bootstrap 5 dropped it, but your app may use it for other things; removing Bootstrap's dependency on it does not require removing jQuery itself.
4. Note the browser floor. Bootstrap 5 drops IE. Bootstrap 6 requires Chrome and Edge 130, Firefox 132, and Safari 18 on macOS and iOS.
---
## Step 2: v4 to v5 — dependencies, build, and Sass
1. Update the dependency and drop the jQuery/Popper v1 pair:
```sh
npm i --save bootstrap@5 @popperjs/core
npm uninstall popper.js
```
`popper.js` (v1) is replaced by `@popperjs/core` (v2). Bootstrap 5's bundle includes Popper; the standalone `bootstrap.js` does not.
2. Switch the Sass compiler to Dart Sass (`npm i --save-dev sass`). Node Sass / LibSass is not supported. Keep using `@import` for now — v5 still uses it; the move to `@use` happens in the v6 stage.
3. Sass maps no longer merge automatically. In v4, adding one key to `$theme-colors` was enough; in v5 you must define the complete map, including the keys you're keeping.
4. Update renamed functions and mixins:
| v4 | v5 |
| --- | --- |
| `color-yiq()` | `color-contrast()` |
| `theme-color()`, `gray()` | Removed — reference `$theme-colors` / `$grays` directly |
| `theme-color-level()` | `tint-color()` / `shade-color()` |
| `lighten()` / `darken()` | `tint-color()` / `shade-color()` |
| `media-breakpoint-down(md)` | `media-breakpoint-down(lg)` — takes the breakpoint itself, not the next one down |
| `media-breakpoint-between(sm, md)` | `media-breakpoint-between(sm, lg)` — same shift on the upper bound |
The breakpoint parameter change is silent: nothing errors, your media queries just apply one breakpoint off. Audit every call.
5. Print styles, `$enable-*` flags, and the utility API all changed shape in v5. If you generated custom utilities with v4 mixins, rewrite them through v5's `$utilities` map.
---
## Step 3: v4 to v5 — JavaScript
1. Namespace every data attribute with `bs`. This is a mechanical but wide-reaching change:
| v4 | v5 |
| --- | --- |
| `data-toggle` | `data-bs-toggle` |
| `data-target` | `data-bs-target` |
| `data-dismiss` | `data-bs-dismiss` |
| `data-parent` | `data-bs-parent` |
| `data-ride` | `data-bs-ride` |
| `data-slide`, `data-slide-to` | `data-bs-slide`, `data-bs-slide-to` |
| `data-spy` | `data-bs-spy` |
| `data-backdrop`, `data-keyboard` | `data-bs-backdrop`, `data-bs-keyboard` |
| `data-content`, `data-placement`, `data-trigger` | `data-bs-content`, `data-bs-placement`, `data-bs-trigger` |
Any option passed as an attribute follows the same rule: `data-