Bootstrap v5 to v6 Migration
Workflow
Work through each step in order. After each step, search the codebase for remaining v5 patterns before moving on.
Step 1: Dependencies & Build
- Update :
"bootstrap": "6.0.0-alpha.1"
- Replace with
- If using Datepicker, add peer dep
- Sass: replace all with (Node Sass is no longer supported)
includes Floating UI and Vanilla Calendar Pro. Standalone
leaves both peer dependencies external. Direct browser loading of the standalone build needs an import map.
scss
// v5
@import "bootstrap/scss/bootstrap";
// v6
@use "bootstrap/scss/bootstrap";
// v6 with overrides — customize CSS tokens through the token maps
@use "bootstrap/scss/bootstrap" with (
$root-tokens: (
--spacer: 1rem,
)
);
Customize by overriding the CSS token maps —
for global tokens and each component's
map — rather than legacy Sass scalars. See
Customize › Sass. Token names are written
unprefixed in Sass (e.g.
,
); Bootstrap's dist/CDN CSS runs PostCSS to add the
prefix (
), so when you compile the source yourself the properties stay unprefixed.
Step 2: CSS Class & Attribute Renames
Responsive & state prefix syntax
v6 moves breakpoints and pseudo-states from infix/suffix to prefix with colon. Also renames
to
.
Pattern: becomes
and
becomes
| v5 | v6 |
|---|
| , | , |
| |
| |
| |
| , | , |
| |
| |
| |
| |
| |
.list-group-horizontal-md
| .md:list-group-horizontal
|
| |
| |
.dialog-fullscreen-sm-down
| .sm-down:dialog-fullscreen
|
| |
| |
The prefix does not identify the query type. Utilities, grid columns, containers, drawers, and responsive tables use viewport queries. Stacks, CSS Grid columns, navbar expansion, horizontal list groups, card groups, and stacked tables use container queries. Add the query container that each component’s documentation requires.
Component renames
Three components have been fully renamed. Find-and-replace these prefixes across classes, data attributes, events, JS imports, and CSS variables.
Modal -> Dialog
| Scope | v5 | v6 |
|---|
| Classes | , .modal-header/body/footer/title
| , .dialog-header/body/footer/title
|
| Sizes | .modal-sm/lg/xl/fullscreen
| .dialog-sm/lg/xl/fullscreen
|
| Data attrs | , | , |
| JS export | | |
| Events | | |
| CSS vars | | |
| Body class | on | on |
Remove
and
wrappers entirely — see Step 3.
Offcanvas -> Drawer
| Scope | v5 | v6 |
|---|
| Classes | , .offcanvas-start/end/top/bottom/header/body/title
| , .drawer-start/end/top/bottom/header/body/title
|
| Data attrs | data-bs-toggle="offcanvas"
, data-bs-dismiss="offcanvas"
| , |
| JS export | | |
| Events | | |
| CSS vars | | |
| Sass | | |
Dropdown -> Menu
| Scope | v5 | v6 |
|---|
| Classes | , , , | , , , |
| Data attrs | data-bs-toggle="dropdown"
| |
| JS export | | |
| Events | | |
| Sass | | |
Also remove:
(no longer needed),
wrapper,
. See Step 3 for new markup.
Button & badge variants -> theme tokens
Per-color classes are replaced by variant +
composition. Built-in theme keys are
,
,
,
,
,
,
, and
. Apply both classes to the same element.
| v5 | v6 |
|---|
| class="btn-solid theme-primary"
|
| class="btn-outline theme-primary"
|
| class="alert theme-primary"
|
| class="badge theme-primary"
|
New button variants:
,
,
,
,
,
.
Use
on a nested subtree to drop inherited
tokens and return to component defaults.
There are no
or
classes. Review v5 light and dark variants manually. Use neutral surface utilities, color modes, or
as appropriate.
Placeholder animations
Use
for the standard loading animation. Replace
with
when you want the old opacity animation.
remains as a compatibility alias.
Utility class renames
| v5 | v6 |
|---|
| , , etc. | , , etc. |
| |
| |
| |
| (on ) |
| |
| Remove (no longer needed) |
| |
| class="bg-primary fg-contrast-primary"
|
| , | , |
Remove
through
and
. Compose their styles with
and
utilities. Review
,
, and
; use the v6 neutral surfaces and color modes described in the migration guide.
Font size classes
v6 replaces the numeric scale with t-shirt sizes (ascending). The full v6 scale is
xs sm md lg xl 2xl 3xl 4xl 5xl 6xl
. The rem values below are the
v5 sizes, shown only to help you match each class.
| v5 (size) | v6 |
|---|
| (2.5rem) | |
| (2rem) | |
| (1.75rem) | |
| (1.5rem) | |
| (1.25rem) | |
| (1rem) | |
Note: in v6,
and larger are
fluid values that scale with the viewport between a min and a max (e.g.
is
clamp(2.25rem, 1.75rem + 2.5vw, 3rem)
);
/
/
stay fixed. So sizes won't match v5 exactly — the mapping preserves the relative scale, not the precise rem.
Spacer scale
Keys 2-5 have changed values. To preserve v5 spacing:
(0.5rem) ->
,
(1rem) ->
,
(1.5rem) ->
,
(3rem) ->
.
| Key | v5 | v6 |
|---|
| 2 | | |
| 3 | | |
| 4 | | |
| 5 | | |
| 6-12 | — | through , in steps |
Form validation
| v5 | v6 |
|---|
| on | on |
| via JS | Remove — handles it |
<div class="valid-tooltip">
| <div class="tooltip valid-tooltip">
|
<div class="invalid-tooltip">
| <div class="tooltip invalid-tooltip">
|
Step 3: Structural HTML Changes
These components have fundamentally new markup, not just class renames.
Dialog (was Modal)
Remove the
and
wrappers. Use a single
element. The backdrop is now native
.
html
<!-- v5 -->
<div class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">Content</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- v6 -->
<dialog class="dialog" id="exampleDialog">
<div class="dialog-header">
<h5 class="dialog-title">Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
</div>
<div class="dialog-body">Content</div>
<div class="dialog-footer">
<button class="btn-solid theme-secondary" data-bs-dismiss="dialog">Close</button>
</div>
</dialog>
Drawer (was Offcanvas)
Use a native
element. A renamed
does not work.
html
<!-- v5 -->
<div class="offcanvas offcanvas-start" id="menu">...</div>
<!-- v6 -->
<dialog class="drawer drawer-start" id="menu">...</dialog>
For responsive navbars, change
data-bs-toggle="collapse"
to
. Replace
.collapse.navbar-collapse
with a
that contains
and
. Rename
to
, and remove
or
.
Accordion
Replace Collapse JS with native
/
. The
attribute creates exclusive groups (replaces
). Add
SVG inside
. Remove
and
. New modifiers:
for compact padding and type,
to space items as separate rounded cards.
html
<!-- v5 -->
<div class="accordion" id="myAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#collapseOne">
Item 1
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#myAccordion">
<div class="accordion-body">Content</div>
</div>
</div>
</div>
<!-- v6 -->
<div class="accordion">
<details class="accordion-item" name="myAccordion" open>
<summary class="accordion-header">
Item 1
<svg class="accordion-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="m2 5 6 6 6-6"/></svg>
</summary>
<div class="accordion-body">Content</div>
</details>
</div>
Cards
Review card markup and any custom card CSS. v6 changes card borders, layout, and several helper classes.
- owns the background, outer border, radius, shadow, and height. Headers and footers draw only their divider border.
- is optional. It is now a flex column that removes direct-child block margins and uses for spacing. Add for a horizontal body.
- Keep on list groups inside cards. Do not add ; that class is not available.
- and no longer add styles. Keep them only as project hooks. still reduces the gap above the subtitle.
- is removed. Use normal links and flex or gap utilities when you need a link row.
- Use for horizontal cards. Use or for the new visual variants.
- Card groups now use a container query. Add to a parent or the cards remain stacked.
Menu (was Dropdown)
Remove
wrapper and
. Flatten
to
. Toggle and
are siblings.
html
<!-- v5 -->
<div class="dropdown">
<button class="btn dropdown-toggle" data-bs-toggle="dropdown">Menu</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Other</a></li>
</ul>
</div>
<!-- v6 -->
<button class="btn-solid theme-secondary" data-bs-toggle="menu">Menu</button>
<div class="menu">
<a class="menu-item" href="#">Action</a>
<hr class="menu-divider">
<a class="menu-item" href="#">Other</a>
</div>
Close button
The markup does
not change —
stays an empty element. Do
not add a child SVG (that double-renders the icon). What changed is internal: v6 draws the icon with a CSS
(
) over
background-color: currentcolor
, so the button now inherits the current text
instead of a fixed image.
html
<!-- v5 and v6 — identical markup -->
<button type="button" class="btn-close" aria-label="Close"></button>
Because the icon is
, the v5
variant is
removed — to get a light close button, set the text color (e.g. place it in a dark
context or add a
/
utility) instead.
Checkbox
Apply
directly on the
. No wrapper and no inline SVG — the mark is a CSS
on
. Radios (
) follow the same pattern. Switches keep a
wrapper around the input.
html
<!-- v5 -->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="check1">
<label class="form-check-label" for="check1">Check me</label>
</div>
<!-- v6 -->
<div class="form-field">
<input type="checkbox" id="check1" class="check">
<label for="check1">Check me</label>
</div>
Radio and switch equivalents:
html
<!-- v6 radio -->
<div class="form-field">
<input type="radio" id="radio1" class="radio">
<label for="radio1">Pick me</label>
</div>
<!-- v6 switch -->
<div class="form-field">
<div class="switch">
<input type="checkbox" id="switch1" role="switch" switch>
</div>
<label for="switch1">Toggle me</label>
</div>
Toggle buttons
Input is now nested inside the label.
goes on the label. No
/
needed.
html
<!-- v5 -->
<input type="checkbox" class="btn-check" id="toggle1" autocomplete="off">
<label class="btn btn-outline-primary" for="toggle1">Toggle</label>
<!-- v6 -->
<label class="btn-check btn-solid theme-primary">
<input type="checkbox" autocomplete="off">
Toggle
</label>
Range
is now a wrapper and a JavaScript hook. Move the class from the input to a wrapper and add
to the input.
html
<!-- v5 -->
<input type="range" class="form-range">
<!-- v6 -->
<div class="form-range">
<input type="range" class="form-range-input">
</div>
Breadcrumbs
Add
on
elements. Add empty
separators between items (replaces
pseudo-elements). An empty divider renders a default chevron via a CSS mask. The default bottom margin is gone (v5 used
).
html
<!-- v5 -->
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active">Library</li>
</ol>
<!-- v6 -->
<ol class="breadcrumb">
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#">Home</a></li>
<li class="breadcrumb-divider"></li>
<li class="breadcrumb-item"><a class="breadcrumb-link active" href="#">Library</a></li>
</ol>
Step 4: JavaScript
ESM-only
All dist files are now ES modules. No more UMD bundles or
global.
html
<!-- v5 -->
<script src="bootstrap.bundle.min.js"></script>
<!-- v6 -->
<script type="module" src="bootstrap.bundle.min.js"></script>
Replace global namespace access with imports:
js
// v5 — global namespace
const tooltip = bootstrap.Tooltip.getOrCreateInstance(el)
js
// v6 — explicit import
import { Tooltip } from './bootstrap.bundle.min.js'
const tooltip = Tooltip.getOrCreateInstance(el)
Data API initialization still runs automatically after you add
. Apply the component and data attribute renames in this skill. Bundler imports (
import { X } from 'bootstrap'
) work as before.
The bundled build includes Floating UI and Vanilla Calendar Pro. The standalone build requires installed peers or a browser import map.
Renamed JS exports
Popper -> Floating UI
Replace
with
. Rename the
option to
on Tooltip, Popover, and Menu.
Lifecycle methods return a promise
,
,
, and
now return a promise that resolves when the transition ends. You can
them instead of listening for
/
. The events still fire.
Hover and focus tooltips stay open while the pointer or focus is on the tip itself (WCAG 1.4.13). Press Escape or leave both the trigger and the tip to hide it.
Popup datepickers close on outside interaction, on <kbd>Escape</kbd>, or when focus leaves the input and calendar.
TypeScript declarations
Bootstrap’s source is now TypeScript. The package includes type declarations, so remove
. Deep
imports resolve to compiled
files.
Removed
Validation JS
js
// v5
document.querySelectorAll('.needs-validation')
form.classList.add('was-validated')
// v6
document.querySelectorAll('form[data-bs-validate]')
// Remove the was-validated line entirely
Step 5: Sass
Renamed files
| v5 | v6 |
|---|
| |
| Removed (merged into ) |
| Removed |
| |
| |
| , , |
| mixins/_form-validation.scss
|
forms/_form-variables.scss
| Removed |
| Removed |
Renamed variables and functions
| v5 | v6 |
|---|
| |
, $border-radius-sm/lg/xl/xxl
, | Removed — see the radius scale below |
| Use secondary color |
| |
| |
| |
| |
| |
| Removed — icon uses |
| / | |
| (returns not ) |
| (in loop mixins) | |
| (CSS var prefix) | Removed — use PostCSS instead |
Border radius scale
The
variables are gone. v6 uses a single base
and a
map (keys
–
, e.g.
,
), exposed as
–
tokens (plus
). The
utilities now span
–
and map to different values than v5, so shift class numbers up to keep the same roundness (e.g.
→
,
→
). Override the base or the map entries rather than the old per-size variables:
scss
// $radius lives in _config.scss, so configure that module before loading Bootstrap
@use "bootstrap/scss/config" with (
$radius: .375rem // scales the whole map
);
@use "bootstrap/scss/bootstrap";
Removed (no replacement)
- — dark mode is always compiled
- , , ,
$accordion-button-focus-border-color
,
- ,
$popover-arrow-outer-color
- , ,
$list-group-item-bg-scale
, $list-group-item-color-scale
$carousel-dark-indicator-active-bg
, $carousel-dark-control-icon-filter
- All variables — use mixin with CSS custom properties
- RFS mixins — use for responsive sizing
- mixin
- Caret mixins (, , , , ) — add an icon to the toggle markup instead
- , , from text color utilities map
Utility API
Removed
,
, and
options. Use
map and
instead.
Theme maps merge
,
,
,
,
, and other global maps use
. Pass only the keys you want to change through
. Set a key to
to drop it. Nested theme-color maps merge one level deep — pass the whole sub-map to change one role.
Root tokens emit on
. Repeat
overrides on
if you use shadow DOM.
Rebuilt behavior & new components
Beyond the renames above, several things changed how a v5 project behaves or what's available. Check the docs for full markup/options.
Carousel — rebuilt on CSS scroll-snap
The markup (
→
→
) and the JS API (
/
/
/
/
,
/
events) are preserved, but several things changed:
- → (boolean, opt-in). becomes ; becomes . Default is , so a v5 carousel that auto-advanced now sits still until you opt in. Interacting with an autoplaying carousel now permanently stops it (WCAG 2.2.2).
- → . → (or the new default ); → . Set via . The option is removed (native scroll handles it).
- Removed classes:
.carousel-control-prev/next
(compose a + + ), (use your own markup), (use ), (now the default), and the transitional .carousel-item-start/end/next/prev
. Overlaid controls now require . Control-icon classes renamed .carousel-control-prev-icon
→ (and ).
ScrollSpy — rebuilt on IntersectionObserver
The
markup and the
event are unchanged, but detection is now driven by an
activation line (no scroll-position polling), and the options changed:
- Removed the deprecated and options (deprecated since v5.1.3) — no longer parsed.
- Added (default ) — positions the activation line as a or value from the top of the scroll root (e.g. to sit below a sticky navbar). This is the everyday knob; use it instead of .
- is now an advanced override (default , was ); when set it takes precedence over and is passed straight to the observer.
- default changed from to .
- With , clicking a link now restores the URL hash () and moves focus to the target once the scroll settles (better keyboard/AT nav).
- Target s are resolved with , so ids with dots, colons, slashes, or percent-encoding work without manual escaping.
Collapse — simplified state and transitions
- Remove selectors and tests that depend on . Collapse now toggles and uses native size interpolation.
- Replace trigger selectors with .
- Remove the constructor option. Call or when initialization must change state.
New components (didn't exist in v5)
| Component | Trigger / hook | Purpose |
|---|
| Combobox | data-bs-toggle="combobox"
| Filterable/autocomplete select built on Menu |
| Chip / Chip input | , () | Tags / tokens + interactive entry |
| Datepicker | data-bs-toggle="datepicker"
| Date picker (peer dep ) |
| Range | (+ , ticks) | Enhanced range slider with a value bubble |
| Strength | | Password-strength meter |
| OTP input | | One-time-code input |
| Nav overflow | wrapper around a | Collapses overflowing nav items into a menu |
| Toggler | | Generic class/attribute toggler |
| Submenu | (within Menu) | Nested menus (, ) |
| Stepper | | Multi-step workflow (CSS-only) |
| Avatar | | Avatars with sizes, status, (CSS-only) |
| Form adorn | | Icon/text decoration on inputs (CSS-only) |
| Prose | / | Rich-typography scoping (CSS-only) |
Removed / changed internals
- , , and removed. Dialog and Drawer use the native element, which provides the backdrop (), the focus trap, and an inert top layer; the body scroll-lock is now CSS (). If you imported
bootstrap/js/src/util/backdrop
, , or directly, they're gone.
- CSS . Component styles are wrapped in cascade layers (
colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities
). Author CSS outside any layer now wins over Bootstrap regardless of source order — if your v5 overrides relied on specificity or load order, re-check them.
- variables removed. The Sass vars and custom properties are gone. Replace
rgba(var(--bs-primary-rgb), .5)
with color-mix(in oklab, var(--bs-primary), transparent 50%)
(or use the color directly).
Step 6: Verify
- Build the project and fix any compilation errors.
- Search for remaining v5 patterns:
- classes/attributes (should be )
- (should be )
- (should be )
- , , , etc. (should use prefix syntax)
- , (should use )
- , (should be )
- in Sass (should be )
- (should be )
- , (should be )
- (should be )
- (should be , , or )
- / (removed — use )
- Sass vars (removed — use / / )
- child inside (should be empty — icon is a CSS mask)
- (removed — set text instead)
- (must be a native )
- (renamed to ); carousel option (now )
.carousel-control-prev/next
, , , (all removed)
- – (should be … )
- / (now / )
- ScrollSpy / options (removed — use )
- inline SVG inside (icon is a CSS mask on the input)
- full-map replacements (v6 merges via )
- , trigger selectors, and the Collapse option
- responsive stacks, CSS Grid, navbars, list groups, card groups, and stacked tables without a query container
- Test in browser — v6 requires support for and .