Migration
Track and review changes to the Bootstrap source files, documentation, and components to help you migrate from v5 to v6.
Upgrade
Modus Bootstrap 6 is a major release with many breaking changes to modernize our codebase, adopt newer build tools, and improve customization. Keep reading for a guide on how to migrate from v5 to v6, and a full changelog of what’s new.
-
Bump your Bootstrap dependency:
{ "dependencies": { "bootstrap": "^6.0.0" } } -
If using all of Bootstrap’s Sass files, include it in your Sass using
@use:@use "bootstrap/scss/bootstrap";With this, you can then easily override Bootstrap’s Sass variables and maps:
@use "bootstrap/scss/bootstrap" with ( $spacer: 1rem, $enable-reduced-motion: true, ); -
If using only certain parts of Bootstrap’s Sass files, you can use
@useto import them individually. Be aware that our Sass file structure has changed and you may need to adjust your imports accordingly.@use "bootstrap/scss/forms"; -
Update HTML and CSS per the changelog and updates in the documentation.
-
Recompile your Sass to see the changes.
Changelog
CSS
- Clarified and simplified CSS-Sass setup. Read more about our approach for how we use Sass and CSS together to build and customize projects for your specific needs, in particular how we use CSS variables inside Sass maps as our first-class customization layer.
- Implemented CSS layers in
_root.scssand applied them to all our Sass files.- Layers are set in
_root.scssand then utilized across separate Sass partials. - We cannot, unfortunately, wrap
@useor@forwardstatements in@layer—Sass expects those to be top level at all times. Also, while CSS allows@import "file.css" layer(name), Sass also does not support that.
- Layers are set in
- New, streamlined color modes and theming.
- Removed
_maps.scss - Removed
_variables-dark.scss - Added
_colors.scss, splitting colors out to their own file - Removed
_variables.scss, consolidating all variables into_config.scss - Added
_theme.scsswhere we setup all our global theming for how colors are applied
- Removed
- Updated lg, xl, and 2xl breakpoints and containers.
- Increased the
lgbreakpoint from 992px to 1024px; its container remains the same at 960px. - Increased the
xlbreakpoint from 1200px to 1280px, and its container from 1140px to 1200px. - Renamed
xxlto2xlfor better scaling with additional custom breakpoints - Increased the
2xlbreakpoint from 1400px to 1536px, and its container from 1320px to 1440px.
- Increased the
- Adopted modern CSS color functions. All Sass color variables now use
oklch()notation (e.g.,$blue: oklch(60% 0.24 240)) and tint/shade scales are generated withcolor-mix(in lab, ...)in the compiled CSS. The v5$*-rgbCSS custom properties andrgba()patterns have been removed. This requires browser support forcolor-mix()andoklch(). - New theme token system with
.theme-*classes. Per-component color variant classes (like.alert-primary,.badge.bg-primary,.btn-primary,.table-primary) are replaced by a composable.theme-{name}pattern. Adding.theme-primaryto a component sets--theme-bg,--theme-fg,--theme-border,--theme-contrast, and other semantic CSS custom properties that the component reads. This applies across buttons, badges, alerts, tables, cards, accordions, and more. - Responsive and state classes now use a prefix instead of an infix or suffix. Class names follow the Tailwind-style
prefix:classpattern (e.g.,md:d-noneinstead ofd-md-none,hover:opacity-50instead ofopacity-50-hover). In HTML, use the unescaped colon:class="md:d-none". This applies to utilities, grid, pseudo-state variants, and all responsive components.
| Category | Before (v5) | After (v6) |
|---|---|---|
| Utilities | .d-md-none, .p-lg-3 | .md:d-none, .lg:p-3 |
| State variants | .opacity-50-hover:hover | .hover\:opacity-50:hover |
| Grid columns | .col-md-6 | .md:col-6 |
| Row columns | .row-cols-md-3 | .md:row-cols-3 |
| Offsets | .offset-md-2 | .md:offset-2 |
| Gutters | .g-md-3, .gx-md-3 | .md:g-3, .md:gx-3 |
| CSS Grid | .g-col-md-4 | .md:g-col-4 |
| Containers | .container-sm | .sm:container |
| Navbar | .navbar-expand-md | .md:navbar-expand |
| Drawer | .offcanvas-md | .md:drawer |
| Tables | .table-responsive-md | .md:table-responsive |
| List group | .list-group-horizontal-md | .md:list-group-horizontal |
| Sticky | .sticky-md-top | .md:sticky-top |
| Stacks | .vstack-md | .md:vstack |
| Dialog | .dialog-fullscreen-sm-down | .sm-down:dialog-fullscreen |
.d-print-none | .print:d-none |
Sass
- Dropped support for Node Sass, including no longer testing any of our source CSS against it.
- Rearranged several Sass files in the process.
- Removed
add()andsubtract()functions. Usecalc()instead. - Removed
create-css-vars()mixin (unused). - Renamed
breakpoint-infix()tobreakpoint-prefix(). The function now returns a prefix string (e.g.,"md\:") instead of an infix (e.g.,"-md"). Theloop-breakpoints-upandloop-breakpoints-downmixins now expose$prefixinstead of$infix. Update any custom Sass that calls these functions or mixins. - CSS variable prefixing now handled by PostCSS. The
$prefixSass variable has been removed. CSS custom properties are now written without a prefix in the Sass source and prefixed automatically viapostcss-prefix-custom-propertiesduring the build. To customize the prefix, update your PostCSS configuration instead of Sass. - Removed RFS (Responsive Font Sizes). The
scss/vendor/_rfs.scssfile and all RFS mixins have been removed. Typography now uses fixedremvalues andclamp()for responsive sizing. If you relied on RFS for automatic font scaling, you’ll need to implement your own responsive typography or useclamp()directly. - Renamed Sass files for consistency.
_placeholders.scssis now_placeholder.scssand_spinners.scssis now_spinner.scss. Update any individual@useimports for these files. - Standardized focus styles with
focus-ringmixin. All component-specific*-focus-box-shadowSass variables (e.g.,$btn-focus-box-shadow,$input-focus-box-shadow,$accordion-button-focus-box-shadow) have been removed. Focus styles are now handled by a shared@mixin focus-ring()using--focus-ring,--focus-ring-width,--focus-ring-offset, and--focus-ring-colorCSS custom properties. Customize focus styles by overriding these tokens in_root.scssinstead of individual Sass variables. - Renamed
$grid-breakpointsto$breakpoints. - Removed
$enable-dark-mode. Dark mode support is always compiled; control it at runtime withdata-bs-theme(or thecolor-mode()mixin) instead of toggling a Sass flag. - Removes all deprecated Sass variables and values:
- Removed
$nested-kbd-font-weight, no replacement. - Removed
muted,black-50, andwhite-50from text colors utilities map - Removed the carousel dark Sass variables (
$carousel-dark-indicator-active-bg,$carousel-dark-control-icon-filter) and the.carousel-darkclass—they’re not reassigned. Dark carousels now usedata-bs-theme="dark"(see the carousel changes below). Carousel captions were removed entirely too. - Removed
$btn-close-white-filter. The close button no longer uses a filter—its icon is a CSS mask painted withcurrentcolor, so it adapts to dark backgrounds automatically. - Removed
$border-radius-xxl, use$border-radius-2xl. - Removed
$text-mutedfor secondary color. - Removed
$hr-bg-colorfor$hr-border-colorand$hr-heightfor$hr-border-width. - Renamed
$zindex-dropdownto$zindex-menu. - Removed unused
$dropdown-header-paddingfor the-x/-ysplit variables. - Removed unused
$accordion-button-focus-border-color. - Removed unused
$tooltip-arrow-color. - Removed unused
$popover-arrow-colorand$popover-arrow-outer-color - Removed unused
$alert-bg-scale,$alert-border-scale, and$alert-color-scale(replaced by theme tokens) - Removed unused
$list-group-item-bg-scaleand$list-group-item-color-scale(replaced by theme tokens)
- Removed
- Removed form validation Sass variables and files.
- Removed
scss/forms/_form-variables.scss. Feedback/tooltip Sass variables and validation icon SVG data URIs are gone. Validation styling now uses theme-derived CSS custom properties. - Renamed
scss/mixins/_forms.scsstoscss/mixins/_form-validation.scss. Contains only theform-validation-state-selectormixin. - Removed
$enable-validation-iconsfromscss/_config.scss. - Replaced
$form-validation-stateswith$validation-states(state name to theme key map).
- Removed
JavaScript
- Bootstrap’s JavaScript is now ESM-only. We no longer ship UMD bundles. All dist files (
bootstrap.js,bootstrap.bundle.js, and their minified versions) are native ES modules. The plugin APIs themselves are unchanged—only how you load and reference them is different.-
CDN
<script>tags must addtype="module":<script type="module" src="bootstrap.bundle.min.js"></script> -
In v5, the UMD bundle automatically created a
window.bootstrapglobal. ES modules don’t do this, so there is no longer abootstrapglobal object. If you called plugin APIs through the global namespace, you must update to explicit imports:Before (v5):
const tooltip = bootstrap.Tooltip.getOrCreateInstance(el)After (v6):
import { Tooltip } from './bootstrap.bundle.min.js' const tooltip = Tooltip.getOrCreateInstance(el) -
Data attribute APIs are unchanged. If you only use
data-bs-toggle,data-bs-dismiss, and other data attributes—without calling the JavaScript API directly—the only change you need is addingtype="module"to your script tag. All data attribute behavior continues to work automatically. -
For modern ESM-based bundler setups (Vite, Webpack 5, Parcel 2, Rollup, etc.), no changes are needed —
import { Tooltip } from 'bootstrap'works as before and now supports full tree shaking. Projects still using CommonJSrequire()calls will need to update to ESMimportsyntax.
-
- Removed the separate
bootstrap.esm.jsandbootstrap.esm.min.jsfiles —bootstrap.jsis now the ESM entry point. - Removed
js/index.umd.jsentry point. - Removed jQuery support and the
js-test-jquerytest target. - Replaced the Dropdown component with Menu. All
.dropdown-*classes are now.menu-*classes, anddata-bs-toggle="dropdown"is nowdata-bs-toggle="menu". See the Menu docs for full details.- Renamed CSS classes:
.dropdown-menuto.menu,.dropdown-itemto.menu-item,.dropdown-dividerto.menu-divider,.dropdown-headerto.menu-header,.dropdown-submenuto.submenu. - Removed the
.dropdown-toggleclass — menu toggles no longer require a toggle class. - Removed the
.dropdownwrapper — no wrapper element is required. The toggle and.menuare direct siblings. - Simplified markup from
<ul><li><a class="dropdown-item">to a flat<div class="menu"><a class="menu-item">structure. - Removed
.dropdown-toggle-split— button group border radius for split menus is now handled automatically via:has(+ .menu). - Renamed the JavaScript export from
DropdowntoMenu— update imports toimport { Menu } from 'bootstrap'. - Renamed events:
show.bs.dropdowntoshow.bs.menu,shown.bs.dropdowntoshown.bs.menu,hide.bs.dropdowntohide.bs.menu,hidden.bs.dropdowntohidden.bs.menu. - Renamed the data key from
bs.dropdowntobs.menu(affectsMenu.getInstance()andMenu.getOrCreateInstance()).
- Renamed CSS classes:
- Added new Combobox component. A searchable select built on top of Menu, with single and multi-select support. See the Combobox docs.
- Replaced Popper.js (
@popperjs/core) with Floating UI (@floating-ui/dom) for menu, tooltip, and popover positioning. ThepopperConfigoption on Tooltip, Popover, and Menu (formerly Dropdown) has been renamed tofloatingConfig. Update any custom positioning configuration accordingly. - Added Vanilla Calendar Pro (
vanilla-calendar-pro) as a peer dependency for the new Datepicker component. - Removed the
jspmconfiguration frompackage.json. - Added
"sideEffects"metadata topackage.jsonto enable tree shaking in bundlers while preserving the Data API event listeners that Bootstrap’s plugins register at the top level. - Added
"exports"map topackage.jsonfor explicit subpath access to source, dist, and Sass files.
Components
- Replaced the Modal component with Dialog. Dialog is built on the native
<dialog>element, usingshowModal()/show()/close()browser APIs. The markup, classes, data attributes, events, CSS variables, and JavaScript API have all changed:- Markup: The
.modal>.modal-dialog>.modal-contentwrapper structure has been replaced by a single<dialog class="dialog">element. Body sections use.dialog-header,.dialog-body, and.dialog-footerdirectly inside the<dialog>. - CSS classes:
.modal→.dialog,.modal-header→.dialog-header,.modal-body→.dialog-body,.modal-footer→.dialog-footer,.modal-title→.dialog-title. The.modal-dialogand.modal-contentwrapper classes have been removed entirely. - Sizes:
.modal-sm→.dialog-sm,.modal-lg→.dialog-lg,.modal-xl→.dialog-xl,.modal-fullscreen→.dialog-fullscreen. - Data attributes:
data-bs-toggle="modal"→data-bs-toggle="dialog",data-bs-dismiss="modal"→data-bs-dismiss="dialog". - JavaScript:
Modal→Dialog— update imports toimport { Dialog } from 'bootstrap'. - Events:
show.bs.modal→show.bs.dialog,shown.bs.modal→shown.bs.dialog,hide.bs.modal→hide.bs.dialog,hidden.bs.modal→hidden.bs.dialog,hidePrevented.bs.modal→hidePrevented.bs.dialog. - Data key:
bs.modal→bs.dialog(affectsDialog.getInstance()andDialog.getOrCreateInstance()). - CSS variables:
--modal-*→--dialog-*. - Backdrop: The
.modal-backdropDOM element and the legacyutil/backdrophelper are gone — Dialog uses the native::backdroppseudo-element withbackdrop-filter: blur()support. - Body scroll prevention:
.modal-openon<body>→.dialog-openon the<body>element. - New variant classes:
.dialog-slide-up,.dialog-slide-down(slide animations),.dialog-instant(no animation),.dialog-static(static backdrop bounce),.dialog-nonmodal(non-modal positioning),.dialog-scrollable. - Non-modal support: Set
modal: falseordata-bs-modal="false"for non-modal dialogs. - Dialog swapping: Triggers inside an open dialog can open a new dialog and close the current one automatically.
- See the Dialog docs for full details.
- Markup: The
- Offcanvas renamed to Drawer. All class names, data attributes, events, CSS variables, and JavaScript APIs have been renamed:
- CSS classes:
.offcanvas→.drawer,.offcanvas-start→.drawer-start,.offcanvas-header→.drawer-header, etc. - Data attributes:
data-bs-toggle="offcanvas"→data-bs-toggle="drawer",data-bs-dismiss="offcanvas"→data-bs-dismiss="drawer" - JavaScript:
bootstrap.Offcanvas→bootstrap.Drawer - Events:
show.bs.offcanvas→show.bs.drawer, etc. - CSS variables:
--offcanvas-*→--drawer-* - Sass:
$zindex-offcanvas→$zindex-drawer
- CSS classes:
- New
.drawer-sheetvariant for flush-to-edge panels with no inset, border-radius, or shadow. - Swipe-to-dismiss gesture support on touch devices for Drawer components. Drawers automatically detect their placement and dismiss on the appropriate swipe direction.
- Dialog and Drawer share
DialogBase— the show/hide/toggle lifecycle, keyboard handling, backdrop clicks, and static backdrop bounce are consolidated in a shared base class. - Reworked button variants. The v5 per-color classes like
.btn-primary,.btn-outline-primary,.btn-secondary, etc. are replaced by a composition of variant + theme classes:.btn-primary→.btn-solid .theme-primary.btn-outline-primary→.btn-outline .theme-primary- New variant classes:
.btn-solid,.btn-outline,.btn-subtle,.btn-text, plus.btn-styledfor gradient/shadow depth and.btn-linkfor link-style buttons. - Color is applied via
.theme-*utility classes (e.g.,.theme-primary,.theme-danger,.theme-success) rather than being baked into each button class. - Shared sizing tokens: Buttons and inputs now share
--btn-input-*CSS variables for consistent sizing. - New
.btn-iconclass for square icon-only buttons withaspect-ratio: 1. - Added
xsbutton size (.btn-xs).
- Rebuilt accordion on native
<details>/<summary>. The markup structure has fundamentally changed:- v5:
.accordion-item>.accordion-header>button.accordion-button+.accordion-collapse>.accordion-body, controlled by the Collapse JavaScript plugin. - v6:
<details class="accordion-item">><summary class="accordion-header">+<div class="accordion-body">, using the browser’s native disclosure widget. No JavaScript dependency for basic open/close behavior. - Exclusive accordion groups (only one item open) are handled via the HTML
nameattribute on<details>elements, replacing thedata-bs-parentapproach. - The
.accordion-buttonand.accordion-collapseclasses have been removed. - The expand/collapse icon now uses an
.accordion-iconelement (typically an SVG) inside the summary, replacing the CSSbackground-imageapproach. - Open state styling uses
details[open]instead of JavaScript-toggled classes. - Theme coloring via
.theme-*classes on the.accordionwrapper.
- v5:
- Rebuilt close button markup.
.btn-closenow renders its icon via a CSSmask-image(--btn-close-icon) tinted withbackground-color: currentcolor, so the button is self-contained—no child<svg>is required. The filter-based dark mode approach ($btn-close-white-filter) has been replaced bycurrentcolorinheritance, and the.btn-close-whiteclass has been removed—on dark backgrounds (e.g.,.text-bg-dark) the icon now inherits the contrast color automatically. - Removed
.alert-dismissible. Dismissible alerts no longer require the.alert-dismissiblemodifier class. Place a.btn-closedirectly inside the alert—the alert's flex layout positions it automatically. Remove any.alert-dismissibleclass from your markup. - Restructured cards. Borders now live on
.card-bodyand.card-listsegments rather than a single outer.cardborder. Added--card-box-shadowand--card-body-gaptokens. New variant classes:.card-translucent(frosted glass effect) and.card-subtle(themed with subtle backgrounds). Horizontal cards use a new.card-rowclass. Removed.card-linkclass. - Card groups now use container queries.
.card-groupswitches to its attached, equal-width row layout with a@containerquery instead of a viewport@mediaquery, so it responds to the width of a parent query container rather than the viewport. Wrap the card group in a query container—e.g. add the.contains-inlineutility to a parent element—or the cards stay stacked. - List group horizontal variants now use container queries. The
.*:list-group-horizontalclasses switch between vertical and horizontal layouts with@containerqueries instead of viewport@mediaqueries, responding to a parent query container rather than the viewport. Wrap the list group in a query container (e.g..contains-inline) for the responsive variants to take effect. - Reworked badge variants. Badge color variants now use
.badge-subtleand.badge-outlinecombined with.theme-*classes (e.g.,.badge-subtle .theme-primary), replacing the v5.bg-primaryutility pattern on badges. - Updated breadcrumb markup. Breadcrumbs now use
.breadcrumb-linkas an interactive element with padding, min-height, and hover background, and explicit.breadcrumb-dividerelements as separators between items. An empty.breadcrumb-dividerrenders a default chevron via a CSSmask-image(--breadcrumb-divider-icon) tinted withbackground-color: currentcolor; add your own SVG, text, or markup inside it to override. This replaces the v5--bs-breadcrumb-dividercontent string on the.breadcrumb-item::beforepseudo-element. - Navbar toggler icon now uses a CSS mask.
.navbar-toggler-iconrenders viamask-image(--navbar-toggler-icon) tinted withbackground-color: currentcolorinstead of an embeddedbackground-imageSVG. The markup stays an empty<span class="navbar-toggler-icon">, but the icon now inherits the current text color (including dark mode), so the separate light/dark toggler SVGs are no longer needed. - Rebuilt the carousel on CSS scroll snap. The slide engine no longer uses
float+translateXclass juggling or a custom swipe handler—.carousel-inneris now a native horizontal scroll-snap container, so sliding, touch dragging, momentum, and keyboard scrolling come from the browser. The markup is unchanged (.carousel→.carousel-inner→.carousel-item), and the public JavaScript API (next,prev,to,cycle,pause) plus theslide.bs.carousel/slid.bs.carouselevents are preserved.- New capabilities: show multiple slides at once, reveal a “peek” of adjacent slides, gaps, center mode, and variable-width slides—all via CSS custom properties (
--carousel-items,--carousel-items-gap,--carousel-items-peek) and the.carousel-center/.carousel-autovariants. - Removed transitional classes
.carousel-item-start,.carousel-item-end,.carousel-item-next, and.carousel-item-prev, plus the.carousel.pointer-eventhelper—the browser tracks scroll position instead of an.activelayout class. (The active slide still gets.activefor styling.) .carousel-fadeis now a stacked-opacity (grid) crossfade animated with a CSS opacity transition over--bs-carousel-fade-duration(it collapses to an instant swap under reduced motion).- Removed the
touchoption. Because.carousel-inneris a native scroll-snap container, horizontal touch dragging is part of the browser’s native scrolling and is no longer toggled by JavaScript. - Active-slide syncing uses an
IntersectionObserver; as a resultslid.bs.carouselfires when the new slide settles into view, and a multi-slideto()jump may emit intermediateslidevents as it scrolls past.
- New capabilities: show multiple slides at once, reveal a “peek” of adjacent slides, gaps, center mode, and variable-width slides—all via CSS custom properties (
- Renamed the carousel
rideoption toautoplay, and made it a boolean. Autoplay is now strictly opt-in: a carousel only autoplays whenautoplayistrue(set viadata-bs-autoplay="true"). The oldrideoption and its string"carousel"value have been removed, as has theride="true"behavior that started autoplaying only after the first user interaction.data-bs-ride="carousel"→data-bs-autoplay="true"data-bs-ride="true"→data-bs-autoplay="true"(it now autoplays on load like any other autoplaying carousel, instead of waiting for the first interaction)- JavaScript:
new bootstrap.Carousel(el, { ride: 'carousel' })→new bootstrap.Carousel(el, { autoplay: true }) - The auto-initialization-on-load selector changed accordingly from
[data-bs-ride="carousel"]to[data-bs-autoplay="true"].
- Autoplaying carousels now stop when the user interacts with them. Clicking a control or indicator, navigating with the keyboard, or swiping permanently stops autoplay instead of resuming it, respecting the visitor’s intent (WCAG 2.2.2). Previously these interactions kept the carousel cycling.
- New
.carousel-control-play-pausecontrol provides a discoverable, accessible button to pause and resume an autoplaying carousel—the mechanism WCAG 2.2.2 requires (a hover-only pause does not qualify). It renders pause/play icons via CSS masks (--carousel-control-pause-icon/--carousel-control-play-icon), swaps its icon andaria-labelwith state, and can also start autoplay on an otherwise static carousel. - Stacked is now the default carousel layout.
.carouselis a flex column, so prev/next controls, indicators, and any custom content sit in the flow above or below the slides. The old.carousel-stackedclass was removed—it’s now the default, so drop it from your markup. - Overlaid controls now require the
.carousel-overlaymodifier. To overlay the prev/next controls, play/pause button, and indicators on top of the slides (the classic v5 look), add.carousel-overlayto the.carouselelement. Without it, those elements lay out in the flow. - Renamed the control-icon classes
.carousel-control-prev-icon→.carousel-icon-prevand.carousel-control-next-icon→.carousel-icon-next. The icons are now painted withbackground-color: currentcolor, so they inherit the surrounding text color (white on the overlay controls, the button color inside.btn-*). Size them with--bs-carousel-control-icon-width. - Removed
.carousel-captionand its--carousel-caption-*tokens. Compose slide content from your own markup inside.carousel-iteminstead, styling and positioning it with utilities or custom CSS. - Replaced the
wrapoption withends. v5'swrap: true | falseis nowends: "loop" | "wrap" | "stop"(default"loop"). Mapwrap: truetoends: "wrap"(or the new default"loop"for the seamless conveyor effect) andwrap: falsetoends: "stop". Set it viadata-bs-endsor theendsoption. Unknown values fall back to"loop". - Removed the
.carousel-control-prev/.carousel-control-nextbutton classes. The absolute, full-height hover targets are gone. Compose a control from a button (e.g..btn-icon) plusdata-bs-slide="prev"/data-bs-slide="next"and a.carousel-icon-prev/.carousel-icon-nextglyph, placed in the flow or inside.carousel-overlay-controls. - Removed the
.carousel-darkclass. Usedata-bs-theme="dark"on the.carousel(typically alongside.carousel-overlay) for reversed contrast. - Removed v5 carousel tokens that no longer have an equivalent:
--carousel-caption-*,--carousel-control-color,--carousel-control-opacity/--carousel-control-hover-opacity,--carousel-control-icon-filter, and--carousel-transition. Indicator, control, and fade styling now derive from the redesigned--bs-carousel-*tokens listed under CSS variables.
Reboot
- Relocated heading classes (like
.h1) and some type classes (.markand.small) to Reboot from_type.scss. This avoids a dependency in Sass modules and we like to avoid extending selectors in general. - Removed the
::-moz-focus-innerstyles, as the pseudo selector is deprecated in Firefox. - Removed
text-transform: nonefrombuttonandselectelements, as Firefox no longer incorrectly inherits text-transform. - Added
accent-color: var(--primary-base)on:root, which applies the primary theme color to native form controls (checkboxes, radios, range inputs, progress bars) globally.
Forms
- Refactor checks, radios, and switches.
- Split apart
_form-check.scssinto separate stylesheets:_checkbox.scss,_radio.scss, and_switch.scss. - Also split apart the documentation pages for checks, radios, and switches.
- Added new CSS variables on each of these components. Side note: we could’ve shared variables here, but chose not to for simplicity’s sake.
- Removed several now unused Sass variables.
- Checkboxes apply the
.checkclass directly on the<input>(no wrapping element) and draw the checked and indeterminate marks with a CSSmask-imageon a::beforepseudo-element tinted with the contrast color—no inline SVG in the DOM. Radios and switches are likewise styled without a wrapping element. - Revamped layout for checks, radios, and switches with labels (and descriptions). We now have custom elements for layout that include basic flexbox styling.
- Refactored toggle buttons to use a nested input structure. The
.btn-checkclass now goes on the label (not the input), with the input nested inside. This eliminates the need forid/forattributes and uses CSS:has()selector instead of sibling selectors. Example:<label class="btn-check btn-solid theme-primary"><input type="checkbox">Toggle</label>.
- Split apart
- Consolidate
.form-selectinto.form-control.- Removed
.form-select—use.form-controlon<select>elements now. Too much abstraction and duplication at the same time. - Adds new CSS variables on
.form-controlfor easier customization without Sass compilation. .form-controlnow has amin-heightat all times as opposed to just on<textarea>elements. This reduces some CSS for us.
- Removed
- Added new Combobox form component. A searchable, filterable select with single and multi-select modes, built on top of the Menu component. See the Combobox docs.
- New
.form-fieldlayout component. Replaces.checkgroupand.radiogroupwrappers with a unified grid-based layout primitive for label + control + help text + validation feedback. Use.form-field,.form-field-content, and.form-field-cardfor structured form layouts, and.form-groupfor grouping related fields. See the Field docs. - Overhauled form validation.
- Client-side validation no longer uses
.was-validatedor bare:valid/:invalidpseudo-classes. Instead, adddata-bs-validateto your<form>to opt in to:user-invalidstyling that activates only after user interaction. To also show success styling, usedata-bs-validate="valid". - Server-side validation with
.is-invalid/.is-validclasses is unchanged and works globally withoutdata-bs-validate. - Custom validation states (e.g., "warning") use only
.is-*classes—no pseudo-class support. - Validation feedback tooltips now require both
.tooltipand.valid-tooltip/.invalid-tooltipclasses (e.g.,<div class="tooltip invalid-tooltip">). - Removed
$enable-validation-iconsand all built-in validation background icons on form controls. - The
$form-validation-statesSass map is replaced by$validation-states, a simpler map pairing state names to theme keys (e.g.,"invalid": "danger"). Styling uses theme-derived CSS custom properties (--danger-fg,--danger-border, etc.) instead of per-state Sass variables. - The
form-validation-statemixin signature changed from($state, $color, $icon, ...)to($state, $theme). - Renamed
scss/mixins/_forms.scsstoscss/mixins/_form-validation.scss. The mixin now only contains the selector logic; the full state mixin lives inscss/forms/_validation.scss. - Removed
scss/forms/_form-variables.scss(feedback variables and icon SVG data URIs). - Validation JS: replace
document.querySelectorAll('.needs-validation')withdocument.querySelectorAll('form[data-bs-validate]')and remove theform.classList.add('was-validated')line.
- Client-side validation no longer uses
- Simplified input groups.
- Removed
.has-validation—border-radius logic no longer branches on it. - Added
.input-group-ignorefor elements that should be skipped by adjoined border-radius rules. - Removed
flex-wrap: wrapfrom.input-group; validation feedback now lives outside the input-group in a parent.form-field.
- Removed
- Updated form label defaults.
.form-labeland.col-form-labelnow default tofont-weight: 500(wasinherit) andfont-size: inherit(wasvar(--font-size-sm)). - Updated form helper text.
.form-textcolor changed fromvar(--fg-3)tovar(--fg-2). Themargin-topfrom--form-text-margin-topis removed; spacing is handled by the parent.form-fieldgrid gap. - Reworked switch internals. The switch thumb is now absolutely positioned with
inset-inline-starttransitions instead of padding-based animation. New tokens:--switch-indicator-width,--switch-indicator-height. Themes overriding old padding-based switch behavior will need updating.
Helpers
- Ratio helpers have been moved to utilities.
- Dropped clearfix helper for
.d-flow-rootutility.
Utilities
- Expanded spacer scale. The
$spacersmap has been expanded from 6 steps (0–5) to 10 steps (0–9) with finer granularity. Note that spacer keys no longer map to the same values as v5:
| Key | v5 value | v6 value |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0.25rem | 0.25rem |
| 2 | 0.5rem | 0.5rem |
| 3 | 1rem | 0.75rem (new) |
| 4 | 1.5rem | 1rem (was key 3) |
| 5 | 3rem | 1.25rem (new) |
| 6 | — | 1.5rem (was key 4) |
| 7 | — | 2rem (new) |
| 8 | — | 2.5rem (new) |
| 9 | — | 3rem (was key 5) |
- New fixed-size scale. A new
$sizesmap (keys 1–12) provides fixed rem-based widths, merged into the width utility. v5 had no equivalent fixed-size scale in$sizes—it only had percentage-based values.
| Key | v5 $sizes | v6 $sizes |
|---|---|---|
| 1 | — | 1rem |
| 2 | — | 2rem |
| 3 | — | 3rem |
| 4 | — | 4rem |
| 5 | — | 5rem |
| 6 | — | 6rem |
| 7 | — | 7rem |
| 8 | — | 8rem |
| 9 | — | 9rem |
| 10 | — | 10rem |
| 11 | — | 11rem |
| 12 | — | 12rem |
| 25 | 25% | 25% (in width utility) |
| 50 | 50% | 50% (in width utility) |
| 75 | 75% | 75% (in width utility) |
| 100 | 100% | 100% (in width utility) |
| auto | auto | auto (in width utility) |
- Font size scale reworked.
.fs-1through.fs-6(numeric, descending size) have been replaced by t-shirt size keys from.fs-xsthrough.fs-6xl(10 steps, ascending). Larger sizes useclamp()for responsive scaling. New.text-{size}utilities set bothfont-sizeandline-heighttogether.
| v5 class | v5 value | v6 class | v6 value |
|---|---|---|---|
.fs-6 | 1rem | .fs-md | 1rem |
.fs-5 | 1.25rem | .fs-lg | clamp(1.25rem, …, 1.5rem) |
.fs-4 | 1.5rem | .fs-xl | clamp(1.5rem, …, 1.75rem) |
.fs-3 | 1.75rem | .fs-2xl | clamp(1.75rem, …, 2rem) |
.fs-2 | 2rem | .fs-3xl | clamp(2rem, …, 2.5rem) |
.fs-1 | 2.5rem | .fs-4xl | clamp(2.25rem, …, 3rem) |
| — | — | .fs-xs | 0.75rem |
| — | — | .fs-sm | 0.875rem |
| — | — | .fs-5xl | clamp(3rem, …, 4rem) |
| — | — | .fs-6xl | clamp(3.75rem, …, 5rem) |
- Border radius tokens replaced with a numeric scale. The named
$border-radius-*Sass variables and--border-radius-*CSS custom properties (-xs,-sm, default,-lg,-xl,-2xl) have been removed in favor of a numeric$radiiSass map (keyed0–9) that generates--radius-0through--radius-9tokens, plus--radius-pill. The scale is driven by a single$radius: .5rembase, so all steps move together when the base changes. To migrate any custom Sass or CSS that referenced the old tokens directly:
| v5 variable | v5 value | v6 token | v6 value |
|---|---|---|---|
$border-radius-xs / --border-radius-xs | 0.25rem | --radius-3 | 0.25rem |
$border-radius-sm / --border-radius-sm | 0.25rem | --radius-3 | 0.25rem |
$border-radius / --border-radius | 0.375rem | --radius-4 | 0.375rem |
$border-radius-lg / --border-radius-lg | 0.5rem | --radius-5 | 0.5rem |
$border-radius-xl / --border-radius-xl | 1rem | --radius-8 | 1rem |
$border-radius-xxl / --border-radius-2xl | 2rem | — (closest: --radius-9 1.5rem) | |
$border-radius-pill / --border-radius-pill | 50rem | --radius-pill | 50rem |
- Border radius utilities expanded and remapped.
.rounded-*is now generated from the$radiimap, so the scale spans.rounded-0through.rounded-9(previously.rounded-0through.rounded-5). The default.roundedstill resolves to0.5rem, but the numbered classes now map to different values than v5:
| Utility class | v5 value | v6 value | v6 token |
|---|---|---|---|
.rounded | 0.375rem | 0.5rem | var(--radius-5) |
.rounded-0 | 0 | 0 | var(--radius-0) |
.rounded-1 | 0.25rem | 0.125rem | var(--radius-1) |
.rounded-2 | 0.375rem | 0.1875rem | var(--radius-2) |
.rounded-3 | 0.5rem | 0.25rem | var(--radius-3) |
.rounded-4 | 1rem | 0.375rem | var(--radius-4) |
.rounded-5 | 2rem | 0.5rem | var(--radius-5) |
.rounded-6 | — | 0.625rem | var(--radius-6) |
.rounded-7 | — | 0.75rem | var(--radius-7) |
.rounded-8 | — | 1rem | var(--radius-8) |
.rounded-9 | — | 1.5rem | var(--radius-9) |
.rounded-circle | 50% | 50% | — |
.rounded-pill | 50rem | 50rem | var(--radius-pill) |
To preserve v5 visual roundness, shift class numbers up the scale (e.g. .rounded-1 → .rounded-3, .rounded-2 → .rounded-4, .rounded-3 → .rounded-5, .rounded-4 → .rounded-8, .rounded-5 → .rounded-9). The .rounded-{top,end,bottom,start}-* directional variants follow the same scale.
- Font weight additions. Added
.fw-medium(500) and.fw-semibold(600) utilities. v5 only hadlighter,light(300),normal(400),bold(700), andbolder. - Negative margins limited. Negative spacers are reduced to only
-1(-0.25rem) and-2(-0.5rem), and only applied tomargin-inline-start(.ms-n1,.ms-n2) andmargin-inline-end(.me--1,.me--2). The v5 full negative margin utilities across all sides have been removed. - Spacing and border utilities now use CSS logical properties.
margin-top→margin-block-start,margin-right→margin-inline-end,padding-left→padding-inline-start,border-right→border-inline-end, etc. Class names (.mt-*,.me-*,.ps-*,.border-end) remain the same, but the underlying CSS properties are now logical, improving RTL and writing-mode support. - Text wrap additions. Added
.text-balanceand.text-prettyvalues to the text-wrap utility. - Color utility renames.
.text-*color utilities have been replaced by.fg-*(foreground) utilities. New.fg-emphasis-*and.fg-contrast-*variants. Background utilities now include.bg-subtle-*and.bg-muted-*in addition to.bg-*. Added.fg-bgand.bg-fgcross-reference utilities; removed.fg-inheritand.bg-inherit. Renamed.bg-opacity-*to.bg-*. Renamed.text-resetto.fg-reset. - Display utilities: added
flow-rootandcontentsoptions. - Sizing utilities:
- Renamed
.mh-*/.mw-*to.max-h-*/.max-w-* - Added
.min-h-*and.min-w-*utilities with two default values,0and100% - Added
auto,min-content,max-content, andfit-contenttowidthandheightutilities.
- Renamed
- Flex & Grid utilities:
- Added
.place-itemsand.justify-itemsutilities. - Added
.grid-cols-*utilities forgrid-template-columns(1–4 and 6 column layouts),.grid-cols-fillfor spanning all columns, and.grid-auto-flowutility.
- Added
- Container query utilities. New
.contains-inlineand.contains-sizeutilities forcontainer-type. - Ratio helpers are now powered by the utility API and use simplified values without
calc(). - State variants now use prefix syntax. Pseudo-state utility classes like hover and focus variants now use a
state:classprefix pattern (e.g.,hover:opacity-50instead ofopacity-50-hover), matching the responsive prefix convention. - Utility API cleanup. Removed
css-var,css-variable-name, andlocal-varsoptions from the utility API. Use thepropertymap approach for CSS custom properties andvariablesfor static CSS custom properties within utility classes.
New components and plugins
- Dialog — replaces Modal, built on the native
<dialog>element. See the Components section above for the full migration. - Stepper — new
.steppercomponent for multi-step workflows with.stepper-itemand.stepper-horizontalvariant. CSS-only. - Avatar — new
.avatarcomponent with sizes (.avatar-xsthrough.avatar-xl), status indicators (.avatar-status .status-online|offline|busy|away), subtle variant, and.avatar-stackfor grouped avatars. - Chip and Chip Input — new
.chipcomponent for tags/tokens and.chip-inputfor interactive chip entry.ChipsJavaScript plugin with events:add.bs.chips,remove.bs.chips,change.bs.chips. - OTP Input — new
.otpcomponent for one-time password fields. Built on a single<input>rendered as separate digit slots for full accessibility.OtpInputJavaScript plugin with events:input.bs.otpInput,complete.bs.otpInput(both exposeevent.value). - Password Strength —
StrengthJavaScript plugin for password strength metering withstrengthChange.bs.strengthevent. - Toggler —
TogglerJavaScript plugin for toggling classes or attributes on elements viadata-bs-toggle="toggler". - Datepicker —
DatepickerJavaScript plugin built on Vanilla Calendar Pro, with events:change.bs.datepicker,show.bs.datepicker,hide.bs.datepicker. - Form Adorn — new
.form-adorncomponent for adding icons or text decoration to form inputs. - Prose — new
.proseclass for rich typography scoping and.not-proseto opt out of prose styles within a prose container. - NavOverflow —
NavOverflowJavaScript plugin for handling overflowing navigation items. - Submenu — nested menu support via
.submenuclass within Menu, withsubmenuTrigger(hover,click, orboth) andsubmenuDelayoptions.
Docs
- Removed all
AddedInbadges. - Rearranged utilities documentation to break apart larger pages that included groups of utilities. Sizing, spacing, flex, type, and more have been broken out into smaller pages with new sub-group headings in the sidebar.